michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko; michael@0: michael@0: import java.io.BufferedReader; michael@0: import java.io.Closeable; michael@0: import java.io.File; michael@0: import java.io.FileReader; michael@0: import java.io.FileOutputStream; michael@0: import java.io.IOException; michael@0: import java.io.InputStream; michael@0: import java.io.InputStreamReader; michael@0: import java.io.OutputStream; michael@0: import java.io.PrintWriter; michael@0: import java.io.StringWriter; michael@0: import java.net.Proxy; michael@0: import java.net.URL; michael@0: import java.nio.ByteBuffer; michael@0: import java.util.ArrayList; michael@0: import java.util.Iterator; michael@0: import java.util.List; michael@0: import java.util.Locale; michael@0: import java.util.Map; michael@0: import java.util.NoSuchElementException; michael@0: import java.util.Queue; michael@0: import java.util.StringTokenizer; michael@0: import java.util.TreeMap; michael@0: import java.util.concurrent.ConcurrentHashMap; michael@0: import java.util.concurrent.ConcurrentLinkedQueue; michael@0: michael@0: import org.mozilla.gecko.favicons.OnFaviconLoadedListener; michael@0: import org.mozilla.gecko.favicons.decoders.FaviconDecoder; michael@0: import org.mozilla.gecko.gfx.BitmapUtils; michael@0: import org.mozilla.gecko.gfx.LayerView; michael@0: import org.mozilla.gecko.gfx.PanZoomController; michael@0: import org.mozilla.gecko.mozglue.GeckoLoader; michael@0: import org.mozilla.gecko.mozglue.JNITarget; michael@0: import org.mozilla.gecko.mozglue.RobocopTarget; michael@0: import org.mozilla.gecko.mozglue.generatorannotations.OptionalGeneratedParameter; michael@0: import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; michael@0: import org.mozilla.gecko.prompts.PromptService; michael@0: import org.mozilla.gecko.util.GeckoEventListener; michael@0: import org.mozilla.gecko.util.HardwareUtils; michael@0: import org.mozilla.gecko.util.NativeJSContainer; michael@0: import org.mozilla.gecko.util.ProxySelector; michael@0: import org.mozilla.gecko.util.ThreadUtils; michael@0: import org.mozilla.gecko.webapp.Allocator; michael@0: michael@0: import android.app.Activity; michael@0: import android.app.ActivityManager; michael@0: import android.app.PendingIntent; michael@0: import android.content.ActivityNotFoundException; michael@0: import android.content.Context; michael@0: import android.content.Intent; michael@0: import android.content.SharedPreferences; michael@0: import android.content.pm.ActivityInfo; michael@0: import android.content.pm.ApplicationInfo; michael@0: import android.content.pm.PackageInfo; michael@0: import android.content.pm.PackageManager; michael@0: import android.content.pm.PackageManager.NameNotFoundException; michael@0: import android.content.pm.ResolveInfo; michael@0: import android.content.pm.ServiceInfo; michael@0: import android.content.pm.Signature; michael@0: import android.content.res.TypedArray; michael@0: import android.graphics.Bitmap; michael@0: import android.graphics.Canvas; michael@0: import android.graphics.Color; michael@0: import android.graphics.ImageFormat; michael@0: import android.graphics.Paint; michael@0: import android.graphics.PixelFormat; michael@0: import android.graphics.Rect; michael@0: import android.graphics.RectF; michael@0: import android.graphics.SurfaceTexture; michael@0: import android.graphics.drawable.BitmapDrawable; michael@0: import android.graphics.drawable.Drawable; michael@0: import android.hardware.Sensor; michael@0: import android.hardware.SensorEventListener; michael@0: import android.hardware.SensorManager; michael@0: import android.location.Criteria; michael@0: import android.location.Location; michael@0: import android.location.LocationListener; michael@0: import android.location.LocationManager; michael@0: import android.media.MediaScannerConnection; michael@0: import android.media.MediaScannerConnection.MediaScannerConnectionClient; michael@0: import android.net.ConnectivityManager; michael@0: import android.net.NetworkInfo; michael@0: import android.net.Uri; michael@0: import android.os.Build; michael@0: import android.os.Handler; michael@0: import android.os.Looper; michael@0: import android.os.Message; michael@0: import android.os.MessageQueue; michael@0: import android.os.SystemClock; michael@0: import android.os.Vibrator; michael@0: import android.provider.Settings; michael@0: import android.telephony.TelephonyManager; michael@0: import android.text.TextUtils; michael@0: import android.util.Base64; michael@0: import android.util.DisplayMetrics; michael@0: import android.util.Log; michael@0: import android.view.ContextThemeWrapper; michael@0: import android.view.HapticFeedbackConstants; michael@0: import android.view.Surface; michael@0: import android.view.SurfaceView; michael@0: import android.view.TextureView; michael@0: import android.view.View; michael@0: import android.view.inputmethod.InputMethodManager; michael@0: import android.webkit.MimeTypeMap; michael@0: import android.webkit.URLUtil; michael@0: import android.widget.AbsoluteLayout; michael@0: import android.widget.Toast; michael@0: michael@0: public class GeckoAppShell michael@0: { michael@0: private static final String LOGTAG = "GeckoAppShell"; michael@0: private static final boolean LOGGING = false; michael@0: michael@0: // We have static members only. michael@0: private GeckoAppShell() { } michael@0: michael@0: private static boolean restartScheduled = false; michael@0: private static GeckoEditableListener editableListener = null; michael@0: michael@0: private static final Queue PENDING_EVENTS = new ConcurrentLinkedQueue(); michael@0: private static final Map ALERT_COOKIES = new ConcurrentHashMap(); michael@0: michael@0: private static volatile boolean locationHighAccuracyEnabled; michael@0: michael@0: // Accessed by NotificationHelper. This should be encapsulated. michael@0: /* package */ static NotificationClient notificationClient; michael@0: michael@0: // See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB. michael@0: private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768; michael@0: michael@0: public static final String SHORTCUT_TYPE_WEBAPP = "webapp"; michael@0: public static final String SHORTCUT_TYPE_BOOKMARK = "bookmark"; michael@0: michael@0: static private int sDensityDpi = 0; michael@0: static private int sScreenDepth = 0; michael@0: michael@0: private static final EventDispatcher sEventDispatcher = new EventDispatcher(); michael@0: michael@0: /* Default colors. */ michael@0: private static final float[] DEFAULT_LAUNCHER_ICON_HSV = { 32.0f, 1.0f, 1.0f }; michael@0: michael@0: /* Is the value in sVibrationEndTime valid? */ michael@0: private static boolean sVibrationMaybePlaying = false; michael@0: michael@0: /* Time (in System.nanoTime() units) when the currently-playing vibration michael@0: * is scheduled to end. This value is valid only when michael@0: * sVibrationMaybePlaying is true. */ michael@0: private static long sVibrationEndTime = 0; michael@0: michael@0: /* Default value of how fast we should hint the Android sensors. */ michael@0: private static int sDefaultSensorHint = 100; michael@0: michael@0: private static Sensor gAccelerometerSensor = null; michael@0: private static Sensor gLinearAccelerometerSensor = null; michael@0: private static Sensor gGyroscopeSensor = null; michael@0: private static Sensor gOrientationSensor = null; michael@0: private static Sensor gProximitySensor = null; michael@0: private static Sensor gLightSensor = null; michael@0: michael@0: /* michael@0: * Keep in sync with constants found here: michael@0: * http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl michael@0: */ michael@0: static public final int WPL_STATE_START = 0x00000001; michael@0: static public final int WPL_STATE_STOP = 0x00000010; michael@0: static public final int WPL_STATE_IS_DOCUMENT = 0x00020000; michael@0: static public final int WPL_STATE_IS_NETWORK = 0x00040000; michael@0: michael@0: /* Keep in sync with constants found here: michael@0: http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsINetworkLinkService.idl michael@0: */ michael@0: static public final int LINK_TYPE_UNKNOWN = 0; michael@0: static public final int LINK_TYPE_ETHERNET = 1; michael@0: static public final int LINK_TYPE_USB = 2; michael@0: static public final int LINK_TYPE_WIFI = 3; michael@0: static public final int LINK_TYPE_WIMAX = 4; michael@0: static public final int LINK_TYPE_2G = 5; michael@0: static public final int LINK_TYPE_3G = 6; michael@0: static public final int LINK_TYPE_4G = 7; michael@0: michael@0: /* The Android-side API: API methods that Android calls */ michael@0: michael@0: // Initialization methods michael@0: public static native void nativeInit(); michael@0: michael@0: // helper methods michael@0: // public static native void setSurfaceView(GeckoSurfaceView sv); michael@0: public static native void setLayerClient(Object client); michael@0: public static native void onResume(); michael@0: public static void callObserver(String observerKey, String topic, String data) { michael@0: sendEventToGecko(GeckoEvent.createCallObserverEvent(observerKey, topic, data)); michael@0: } michael@0: public static void removeObserver(String observerKey) { michael@0: sendEventToGecko(GeckoEvent.createRemoveObserverEvent(observerKey)); michael@0: } michael@0: public static native Message getNextMessageFromQueue(MessageQueue queue); michael@0: public static native void onSurfaceTextureFrameAvailable(Object surfaceTexture, int id); michael@0: public static native void dispatchMemoryPressure(); michael@0: michael@0: public static void registerGlobalExceptionHandler() { michael@0: Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { michael@0: @Override michael@0: public void uncaughtException(Thread thread, Throwable e) { michael@0: handleUncaughtException(thread, e); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: private static String getStackTraceString(Throwable e) { michael@0: StringWriter sw = new StringWriter(); michael@0: PrintWriter pw = new PrintWriter(sw); michael@0: e.printStackTrace(pw); michael@0: pw.flush(); michael@0: return sw.toString(); michael@0: } michael@0: michael@0: private static native void reportJavaCrash(String stackTrace); michael@0: michael@0: public static void notifyUriVisited(String uri) { michael@0: sendEventToGecko(GeckoEvent.createVisitedEvent(uri)); michael@0: } michael@0: michael@0: public static native void processNextNativeEvent(boolean mayWait); michael@0: michael@0: public static native void notifyBatteryChange(double aLevel, boolean aCharging, double aRemainingTime); michael@0: michael@0: public static native void scheduleComposite(); michael@0: michael@0: // Resuming the compositor is a synchronous request, so be michael@0: // careful of possible deadlock. Resuming the compositor will also cause michael@0: // a composition, so there is no need to schedule a composition after michael@0: // resuming. michael@0: public static native void scheduleResumeComposition(int width, int height); michael@0: michael@0: public static native float computeRenderIntegrity(); michael@0: michael@0: public static native SurfaceBits getSurfaceBits(Surface surface); michael@0: michael@0: public static native void onFullScreenPluginHidden(View view); michael@0: michael@0: public static class CreateShortcutFaviconLoadedListener implements OnFaviconLoadedListener { michael@0: private final String title; michael@0: private final String url; michael@0: michael@0: public CreateShortcutFaviconLoadedListener(final String url, final String title) { michael@0: this.url = url; michael@0: this.title = title; michael@0: } michael@0: michael@0: @Override michael@0: public void onFaviconLoaded(String pageUrl, String faviconURL, Bitmap favicon) { michael@0: GeckoAppShell.createShortcut(title, url, url, favicon, ""); michael@0: } michael@0: } michael@0: michael@0: private static final class GeckoMediaScannerClient implements MediaScannerConnectionClient { michael@0: private final String mFile; michael@0: private final String mMimeType; michael@0: private MediaScannerConnection mScanner; michael@0: michael@0: public static void startScan(Context context, String file, String mimeType) { michael@0: new GeckoMediaScannerClient(context, file, mimeType); michael@0: } michael@0: michael@0: private GeckoMediaScannerClient(Context context, String file, String mimeType) { michael@0: mFile = file; michael@0: mMimeType = mimeType; michael@0: mScanner = new MediaScannerConnection(context, this); michael@0: mScanner.connect(); michael@0: } michael@0: michael@0: @Override michael@0: public void onMediaScannerConnected() { michael@0: mScanner.scanFile(mFile, mMimeType); michael@0: } michael@0: michael@0: @Override michael@0: public void onScanCompleted(String path, Uri uri) { michael@0: if(path.equals(mFile)) { michael@0: mScanner.disconnect(); michael@0: mScanner = null; michael@0: } michael@0: } michael@0: } michael@0: michael@0: private static LayerView sLayerView; michael@0: michael@0: public static void setLayerView(LayerView lv) { michael@0: sLayerView = lv; michael@0: } michael@0: michael@0: @RobocopTarget michael@0: public static LayerView getLayerView() { michael@0: return sLayerView; michael@0: } michael@0: michael@0: public static void runGecko(String apkPath, String args, String url, String type) { michael@0: // Preparation for pumpMessageLoop() michael@0: MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() { michael@0: @Override public boolean queueIdle() { michael@0: final Handler geckoHandler = ThreadUtils.sGeckoHandler; michael@0: Message idleMsg = Message.obtain(geckoHandler); michael@0: // Use |Message.obj == GeckoHandler| to identify our "queue is empty" message michael@0: idleMsg.obj = geckoHandler; michael@0: geckoHandler.sendMessageAtFrontOfQueue(idleMsg); michael@0: // Keep this IdleHandler michael@0: return true; michael@0: } michael@0: }; michael@0: Looper.myQueue().addIdleHandler(idleHandler); michael@0: michael@0: // run gecko -- it will spawn its own thread michael@0: GeckoAppShell.nativeInit(); michael@0: michael@0: if (sLayerView != null) michael@0: GeckoAppShell.setLayerClient(sLayerView.getLayerClientObject()); michael@0: michael@0: // First argument is the .apk path michael@0: String combinedArgs = apkPath + " -greomni " + apkPath; michael@0: if (args != null) michael@0: combinedArgs += " " + args; michael@0: if (url != null) michael@0: combinedArgs += " -url " + url; michael@0: if (type != null) michael@0: combinedArgs += " " + type; michael@0: michael@0: DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); michael@0: combinedArgs += " -width " + metrics.widthPixels + " -height " + metrics.heightPixels; michael@0: michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: geckoLoaded(); michael@0: } michael@0: }); michael@0: michael@0: // and go michael@0: Log.d(LOGTAG, "GeckoLoader.nativeRun " + combinedArgs); michael@0: GeckoLoader.nativeRun(combinedArgs); michael@0: michael@0: // Remove pumpMessageLoop() idle handler michael@0: Looper.myQueue().removeIdleHandler(idleHandler); michael@0: } michael@0: michael@0: // Called on the UI thread after Gecko loads. michael@0: private static void geckoLoaded() { michael@0: GeckoEditable editable = new GeckoEditable(); michael@0: // install the gecko => editable listener michael@0: editableListener = editable; michael@0: } michael@0: michael@0: static void sendPendingEventsToGecko() { michael@0: try { michael@0: while (!PENDING_EVENTS.isEmpty()) { michael@0: final GeckoEvent e = PENDING_EVENTS.poll(); michael@0: notifyGeckoOfEvent(e); michael@0: } michael@0: } catch (NoSuchElementException e) {} michael@0: } michael@0: michael@0: /** michael@0: * If the Gecko thread is running, immediately dispatches the event to michael@0: * Gecko. michael@0: * michael@0: * If the Gecko thread is not running, queues the event. If the queue is michael@0: * full, throws {@link IllegalStateException}. michael@0: * michael@0: * Queued events will be dispatched in order of arrival when the Gecko michael@0: * thread becomes live. michael@0: * michael@0: * This method can be called from any thread. michael@0: * michael@0: * @param e michael@0: * the event to dispatch. Cannot be null. michael@0: */ michael@0: @RobocopTarget michael@0: public static void sendEventToGecko(GeckoEvent e) { michael@0: if (e == null) { michael@0: throw new IllegalArgumentException("e cannot be null."); michael@0: } michael@0: michael@0: if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) { michael@0: notifyGeckoOfEvent(e); michael@0: // Gecko will copy the event data into a normal C++ object. We can recycle the event now. michael@0: e.recycle(); michael@0: return; michael@0: } michael@0: michael@0: // Throws if unable to add the event due to capacity restrictions. michael@0: PENDING_EVENTS.add(e); michael@0: } michael@0: michael@0: // Tell the Gecko event loop that an event is available. michael@0: public static native void notifyGeckoOfEvent(GeckoEvent event); michael@0: michael@0: /* michael@0: * The Gecko-side API: API methods that Gecko calls michael@0: */ michael@0: michael@0: @WrapElementForJNI(allowMultithread = true, generateStatic = true, noThrow = true) michael@0: public static void handleUncaughtException(Thread thread, Throwable e) { michael@0: if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExited)) { michael@0: // We've called System.exit. All exceptions after this point are Android michael@0: // berating us for being nasty to it. michael@0: return; michael@0: } michael@0: michael@0: if (thread == null) { michael@0: thread = Thread.currentThread(); michael@0: } michael@0: // If the uncaught exception was rethrown, walk the exception `cause` chain to find michael@0: // the original exception so Socorro can correctly collate related crash reports. michael@0: Throwable cause; michael@0: while ((cause = e.getCause()) != null) { michael@0: e = cause; michael@0: } michael@0: michael@0: try { michael@0: Log.e(LOGTAG, ">>> REPORTING UNCAUGHT EXCEPTION FROM THREAD " michael@0: + thread.getId() + " (\"" + thread.getName() + "\")", e); michael@0: michael@0: Thread mainThread = ThreadUtils.getUiThread(); michael@0: if (mainThread != null && thread != mainThread) { michael@0: Log.e(LOGTAG, "Main thread stack:"); michael@0: for (StackTraceElement ste : mainThread.getStackTrace()) { michael@0: Log.e(LOGTAG, ste.toString()); michael@0: } michael@0: } michael@0: michael@0: if (e instanceof OutOfMemoryError) { michael@0: SharedPreferences prefs = getSharedPreferences(); michael@0: SharedPreferences.Editor editor = prefs.edit(); michael@0: editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, true); michael@0: editor.commit(); michael@0: } michael@0: } finally { michael@0: reportJavaCrash(getStackTraceString(e)); michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI(generateStatic = true) michael@0: public static void notifyIME(int type) { michael@0: if (editableListener != null) { michael@0: editableListener.notifyIME(type); michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI(generateStatic = true) michael@0: public static void notifyIMEContext(int state, String typeHint, michael@0: String modeHint, String actionHint) { michael@0: if (editableListener != null) { michael@0: editableListener.notifyIMEContext(state, typeHint, michael@0: modeHint, actionHint); michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI(generateStatic = true) michael@0: public static void notifyIMEChange(String text, int start, int end, int newEnd) { michael@0: if (newEnd < 0) { // Selection change michael@0: editableListener.onSelectionChange(start, end); michael@0: } else { // Text change michael@0: editableListener.onTextChange(text, start, end, newEnd); michael@0: } michael@0: } michael@0: michael@0: private static final Object sEventAckLock = new Object(); michael@0: private static boolean sWaitingForEventAck; michael@0: michael@0: // Block the current thread until the Gecko event loop is caught up michael@0: public static void sendEventToGeckoSync(GeckoEvent e) { michael@0: e.setAckNeeded(true); michael@0: michael@0: long time = SystemClock.uptimeMillis(); michael@0: boolean isUiThread = ThreadUtils.isOnUiThread(); michael@0: michael@0: synchronized (sEventAckLock) { michael@0: if (sWaitingForEventAck) { michael@0: // should never happen since we always leave it as false when we exit this function. michael@0: Log.e(LOGTAG, "geckoEventSync() may have been called twice concurrently!", new Exception()); michael@0: // fall through for graceful handling michael@0: } michael@0: michael@0: sendEventToGecko(e); michael@0: sWaitingForEventAck = true; michael@0: while (true) { michael@0: try { michael@0: sEventAckLock.wait(1000); michael@0: } catch (InterruptedException ie) { michael@0: } michael@0: if (!sWaitingForEventAck) { michael@0: // response received michael@0: break; michael@0: } michael@0: long waited = SystemClock.uptimeMillis() - time; michael@0: Log.d(LOGTAG, "Gecko event sync taking too long: " + waited + "ms"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Signal the Java thread that it's time to wake up michael@0: @WrapElementForJNI michael@0: public static void acknowledgeEvent() { michael@0: synchronized (sEventAckLock) { michael@0: sWaitingForEventAck = false; michael@0: sEventAckLock.notifyAll(); michael@0: } michael@0: } michael@0: michael@0: private static float getLocationAccuracy(Location location) { michael@0: float radius = location.getAccuracy(); michael@0: return (location.hasAccuracy() && radius > 0) ? radius : 1001; michael@0: } michael@0: michael@0: private static Location getLastKnownLocation(LocationManager lm) { michael@0: Location lastKnownLocation = null; michael@0: List providers = lm.getAllProviders(); michael@0: michael@0: for (String provider : providers) { michael@0: Location location = lm.getLastKnownLocation(provider); michael@0: if (location == null) { michael@0: continue; michael@0: } michael@0: michael@0: if (lastKnownLocation == null) { michael@0: lastKnownLocation = location; michael@0: continue; michael@0: } michael@0: michael@0: long timeDiff = location.getTime() - lastKnownLocation.getTime(); michael@0: if (timeDiff > 0 || michael@0: (timeDiff == 0 && michael@0: getLocationAccuracy(location) < getLocationAccuracy(lastKnownLocation))) { michael@0: lastKnownLocation = location; michael@0: } michael@0: } michael@0: michael@0: return lastKnownLocation; michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void enableLocation(final boolean enable) { michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: LocationManager lm = getLocationManager(getContext()); michael@0: if (lm == null) { michael@0: return; michael@0: } michael@0: michael@0: if (enable) { michael@0: Location lastKnownLocation = getLastKnownLocation(lm); michael@0: if (lastKnownLocation != null) { michael@0: getGeckoInterface().getLocationListener().onLocationChanged(lastKnownLocation); michael@0: } michael@0: michael@0: Criteria criteria = new Criteria(); michael@0: criteria.setSpeedRequired(false); michael@0: criteria.setBearingRequired(false); michael@0: criteria.setAltitudeRequired(false); michael@0: if (locationHighAccuracyEnabled) { michael@0: criteria.setAccuracy(Criteria.ACCURACY_FINE); michael@0: criteria.setCostAllowed(true); michael@0: criteria.setPowerRequirement(Criteria.POWER_HIGH); michael@0: } else { michael@0: criteria.setAccuracy(Criteria.ACCURACY_COARSE); michael@0: criteria.setCostAllowed(false); michael@0: criteria.setPowerRequirement(Criteria.POWER_LOW); michael@0: } michael@0: michael@0: String provider = lm.getBestProvider(criteria, true); michael@0: if (provider == null) michael@0: return; michael@0: michael@0: Looper l = Looper.getMainLooper(); michael@0: lm.requestLocationUpdates(provider, 100, (float).5, getGeckoInterface().getLocationListener(), l); michael@0: } else { michael@0: lm.removeUpdates(getGeckoInterface().getLocationListener()); michael@0: } michael@0: } michael@0: }); michael@0: } michael@0: michael@0: private static LocationManager getLocationManager(Context context) { michael@0: try { michael@0: return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); michael@0: } catch (NoSuchFieldError e) { michael@0: // Some Tegras throw exceptions about missing the CONTROL_LOCATION_UPDATES permission, michael@0: // which allows enabling/disabling location update notifications from the cell radio. michael@0: // CONTROL_LOCATION_UPDATES is not for use by normal applications, but we might be michael@0: // hitting this problem if the Tegras are confused about missing cell radios. michael@0: Log.e(LOGTAG, "LOCATION_SERVICE not found?!", e); michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void enableLocationHighAccuracy(final boolean enable) { michael@0: locationHighAccuracyEnabled = enable; michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void enableSensor(int aSensortype) { michael@0: GeckoInterface gi = getGeckoInterface(); michael@0: if (gi == null) michael@0: return; michael@0: SensorManager sm = (SensorManager) michael@0: getContext().getSystemService(Context.SENSOR_SERVICE); michael@0: michael@0: switch(aSensortype) { michael@0: case GeckoHalDefines.SENSOR_ORIENTATION: michael@0: if(gOrientationSensor == null) michael@0: gOrientationSensor = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION); michael@0: if (gOrientationSensor != null) michael@0: sm.registerListener(gi.getSensorEventListener(), gOrientationSensor, sDefaultSensorHint); michael@0: break; michael@0: michael@0: case GeckoHalDefines.SENSOR_ACCELERATION: michael@0: if(gAccelerometerSensor == null) michael@0: gAccelerometerSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); michael@0: if (gAccelerometerSensor != null) michael@0: sm.registerListener(gi.getSensorEventListener(), gAccelerometerSensor, sDefaultSensorHint); michael@0: break; michael@0: michael@0: case GeckoHalDefines.SENSOR_PROXIMITY: michael@0: if(gProximitySensor == null ) michael@0: gProximitySensor = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY); michael@0: if (gProximitySensor != null) michael@0: sm.registerListener(gi.getSensorEventListener(), gProximitySensor, SensorManager.SENSOR_DELAY_NORMAL); michael@0: break; michael@0: michael@0: case GeckoHalDefines.SENSOR_LIGHT: michael@0: if(gLightSensor == null) michael@0: gLightSensor = sm.getDefaultSensor(Sensor.TYPE_LIGHT); michael@0: if (gLightSensor != null) michael@0: sm.registerListener(gi.getSensorEventListener(), gLightSensor, SensorManager.SENSOR_DELAY_NORMAL); michael@0: break; michael@0: michael@0: case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION: michael@0: if(gLinearAccelerometerSensor == null) michael@0: gLinearAccelerometerSensor = sm.getDefaultSensor(10 /* API Level 9 - TYPE_LINEAR_ACCELERATION */); michael@0: if (gLinearAccelerometerSensor != null) michael@0: sm.registerListener(gi.getSensorEventListener(), gLinearAccelerometerSensor, sDefaultSensorHint); michael@0: break; michael@0: michael@0: case GeckoHalDefines.SENSOR_GYROSCOPE: michael@0: if(gGyroscopeSensor == null) michael@0: gGyroscopeSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE); michael@0: if (gGyroscopeSensor != null) michael@0: sm.registerListener(gi.getSensorEventListener(), gGyroscopeSensor, sDefaultSensorHint); michael@0: break; michael@0: default: michael@0: Log.w(LOGTAG, "Error! Can't enable unknown SENSOR type " + aSensortype); michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void disableSensor(int aSensortype) { michael@0: GeckoInterface gi = getGeckoInterface(); michael@0: if (gi == null) michael@0: return; michael@0: michael@0: SensorManager sm = (SensorManager) michael@0: getContext().getSystemService(Context.SENSOR_SERVICE); michael@0: michael@0: switch (aSensortype) { michael@0: case GeckoHalDefines.SENSOR_ORIENTATION: michael@0: if (gOrientationSensor != null) michael@0: sm.unregisterListener(gi.getSensorEventListener(), gOrientationSensor); michael@0: break; michael@0: michael@0: case GeckoHalDefines.SENSOR_ACCELERATION: michael@0: if (gAccelerometerSensor != null) michael@0: sm.unregisterListener(gi.getSensorEventListener(), gAccelerometerSensor); michael@0: break; michael@0: michael@0: case GeckoHalDefines.SENSOR_PROXIMITY: michael@0: if (gProximitySensor != null) michael@0: sm.unregisterListener(gi.getSensorEventListener(), gProximitySensor); michael@0: break; michael@0: michael@0: case GeckoHalDefines.SENSOR_LIGHT: michael@0: if (gLightSensor != null) michael@0: sm.unregisterListener(gi.getSensorEventListener(), gLightSensor); michael@0: break; michael@0: michael@0: case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION: michael@0: if (gLinearAccelerometerSensor != null) michael@0: sm.unregisterListener(gi.getSensorEventListener(), gLinearAccelerometerSensor); michael@0: break; michael@0: michael@0: case GeckoHalDefines.SENSOR_GYROSCOPE: michael@0: if (gGyroscopeSensor != null) michael@0: sm.unregisterListener(gi.getSensorEventListener(), gGyroscopeSensor); michael@0: break; michael@0: default: michael@0: Log.w(LOGTAG, "Error! Can't disable unknown SENSOR type " + aSensortype); michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void moveTaskToBack() { michael@0: if (getGeckoInterface() != null) michael@0: getGeckoInterface().getActivity().moveTaskToBack(true); michael@0: } michael@0: michael@0: public static void returnIMEQueryResult(String result, int selectionStart, int selectionLength) { michael@0: // This method may be called from JNI to report Gecko's current selection indexes, but michael@0: // Native Fennec doesn't care because the Java code already knows the selection indexes. michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "NotifyXreExit") michael@0: static void onXreExit() { michael@0: // The launch state can only be Launched or GeckoRunning at this point michael@0: GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoExiting); michael@0: if (getGeckoInterface() != null) { michael@0: if (restartScheduled) { michael@0: getGeckoInterface().doRestart(); michael@0: } else { michael@0: getGeckoInterface().getActivity().finish(); michael@0: } michael@0: } michael@0: michael@0: systemExit(); michael@0: } michael@0: michael@0: static void systemExit() { michael@0: Log.d(LOGTAG, "Killing via System.exit()"); michael@0: GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoExited); michael@0: System.exit(0); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: static void scheduleRestart() { michael@0: restartScheduled = true; michael@0: } michael@0: michael@0: public static Intent getWebappIntent(String aURI, String aOrigin, String aTitle, Bitmap aIcon) { michael@0: Intent intent; michael@0: michael@0: if (AppConstants.MOZ_ANDROID_SYNTHAPKS) { michael@0: Allocator slots = Allocator.getInstance(getContext()); michael@0: int index = slots.getIndexForOrigin(aOrigin); michael@0: michael@0: if (index == -1) { michael@0: return null; michael@0: } michael@0: String packageName = slots.getAppForIndex(index); michael@0: intent = getContext().getPackageManager().getLaunchIntentForPackage(packageName); michael@0: if (aURI != null) { michael@0: intent.setData(Uri.parse(aURI)); michael@0: } michael@0: } else { michael@0: int index; michael@0: if (aIcon != null && !TextUtils.isEmpty(aTitle)) michael@0: index = WebappAllocator.getInstance(getContext()).findAndAllocateIndex(aOrigin, aTitle, aIcon); michael@0: else michael@0: index = WebappAllocator.getInstance(getContext()).getIndexForApp(aOrigin); michael@0: michael@0: if (index == -1) michael@0: return null; michael@0: michael@0: intent = getWebappIntent(index, aURI); michael@0: } michael@0: michael@0: return intent; michael@0: } michael@0: michael@0: // The old implementation of getWebappIntent. Not used by MOZ_ANDROID_SYNTHAPKS. michael@0: public static Intent getWebappIntent(int aIndex, String aURI) { michael@0: Intent intent = new Intent(); michael@0: intent.setAction(GeckoApp.ACTION_WEBAPP_PREFIX + aIndex); michael@0: intent.setData(Uri.parse(aURI)); michael@0: intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, michael@0: AppConstants.ANDROID_PACKAGE_NAME + ".WebApps$WebApp" + aIndex); michael@0: return intent; michael@0: } michael@0: michael@0: // "Installs" an application by creating a shortcut michael@0: // This is the entry point from AndroidBridge.h michael@0: @WrapElementForJNI michael@0: static void createShortcut(String aTitle, String aURI, String aIconData, String aType) { michael@0: if ("webapp".equals(aType)) { michael@0: Log.w(LOGTAG, "createShortcut with no unique URI should not be used for aType = webapp!"); michael@0: } michael@0: michael@0: createShortcut(aTitle, aURI, aURI, aIconData, aType); michael@0: } michael@0: michael@0: // For non-webapps. michael@0: public static void createShortcut(String aTitle, String aURI, Bitmap aBitmap, String aType) { michael@0: createShortcut(aTitle, aURI, aURI, aBitmap, aType); michael@0: } michael@0: michael@0: // Internal, for webapps. michael@0: static void createShortcut(final String aTitle, final String aURI, final String aUniqueURI, final String aIconData, final String aType) { michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: // TODO: use the cache. Bug 961600. michael@0: Bitmap icon = FaviconDecoder.getMostSuitableBitmapFromDataURI(aIconData, getPreferredIconSize()); michael@0: GeckoAppShell.doCreateShortcut(aTitle, aURI, aURI, icon, aType); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: public static void createShortcut(final String aTitle, final String aURI, final String aUniqueURI, michael@0: final Bitmap aIcon, final String aType) { michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: GeckoAppShell.doCreateShortcut(aTitle, aURI, aUniqueURI, aIcon, aType); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Call this method only on the background thread. michael@0: */ michael@0: private static void doCreateShortcut(final String aTitle, final String aURI, final String aUniqueURI, michael@0: final Bitmap aIcon, final String aType) { michael@0: // The intent to be launched by the shortcut. michael@0: Intent shortcutIntent; michael@0: if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP)) { michael@0: shortcutIntent = getWebappIntent(aURI, aUniqueURI, aTitle, aIcon); michael@0: } else { michael@0: shortcutIntent = new Intent(); michael@0: shortcutIntent.setAction(GeckoApp.ACTION_BOOKMARK); michael@0: shortcutIntent.setData(Uri.parse(aURI)); michael@0: shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, michael@0: AppConstants.BROWSER_INTENT_CLASS_NAME); michael@0: } michael@0: michael@0: Intent intent = new Intent(); michael@0: intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); michael@0: intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon, aType)); michael@0: michael@0: if (aTitle != null) { michael@0: intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle); michael@0: } else { michael@0: intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI); michael@0: } michael@0: michael@0: // Do not allow duplicate items. michael@0: intent.putExtra("duplicate", false); michael@0: michael@0: intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); michael@0: getContext().sendBroadcast(intent); michael@0: } michael@0: michael@0: public static void removeShortcut(final String aTitle, final String aURI, final String aType) { michael@0: removeShortcut(aTitle, aURI, null, aType); michael@0: } michael@0: michael@0: public static void removeShortcut(final String aTitle, final String aURI, final String aUniqueURI, final String aType) { michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: // the intent to be launched by the shortcut michael@0: Intent shortcutIntent; michael@0: if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP)) { michael@0: shortcutIntent = getWebappIntent(aURI, aUniqueURI, "", null); michael@0: if (shortcutIntent == null) michael@0: return; michael@0: } else { michael@0: shortcutIntent = new Intent(); michael@0: shortcutIntent.setAction(GeckoApp.ACTION_BOOKMARK); michael@0: shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, michael@0: AppConstants.BROWSER_INTENT_CLASS_NAME); michael@0: shortcutIntent.setData(Uri.parse(aURI)); michael@0: } michael@0: michael@0: Intent intent = new Intent(); michael@0: intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); michael@0: if (aTitle != null) michael@0: intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle); michael@0: else michael@0: intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI); michael@0: michael@0: intent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT"); michael@0: getContext().sendBroadcast(intent); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: @JNITarget michael@0: static public int getPreferredIconSize() { michael@0: if (android.os.Build.VERSION.SDK_INT >= 11) { michael@0: ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); michael@0: return am.getLauncherLargeIconSize(); michael@0: } else { michael@0: switch (getDpi()) { michael@0: case DisplayMetrics.DENSITY_MEDIUM: michael@0: return 48; michael@0: case DisplayMetrics.DENSITY_XHIGH: michael@0: return 96; michael@0: case DisplayMetrics.DENSITY_HIGH: michael@0: default: michael@0: return 72; michael@0: } michael@0: } michael@0: } michael@0: michael@0: static private Bitmap getLauncherIcon(Bitmap aSource, String aType) { michael@0: final int kOffset = 6; michael@0: final int kRadius = 5; michael@0: int size = getPreferredIconSize(); michael@0: int insetSize = aSource != null ? size * 2 / 3 : size; michael@0: michael@0: Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); michael@0: Canvas canvas = new Canvas(bitmap); michael@0: michael@0: michael@0: // draw a base color michael@0: Paint paint = new Paint(); michael@0: if (aSource == null) { michael@0: // If we aren't drawing a favicon, just use an orange color. michael@0: paint.setColor(Color.HSVToColor(DEFAULT_LAUNCHER_ICON_HSV)); michael@0: canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint); michael@0: } else if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP) || aSource.getWidth() >= insetSize || aSource.getHeight() >= insetSize) { michael@0: // otherwise, if this is a webapp or if the icons is lare enough, just draw it michael@0: Rect iconBounds = new Rect(0, 0, size, size); michael@0: canvas.drawBitmap(aSource, null, iconBounds, null); michael@0: return bitmap; michael@0: } else { michael@0: // otherwise use the dominant color from the icon + a layer of transparent white to lighten it somewhat michael@0: int color = BitmapUtils.getDominantColor(aSource); michael@0: paint.setColor(color); michael@0: canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint); michael@0: paint.setColor(Color.argb(100, 255, 255, 255)); michael@0: canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint); michael@0: } michael@0: michael@0: // draw the overlay michael@0: Bitmap overlay = BitmapUtils.decodeResource(getContext(), R.drawable.home_bg); michael@0: canvas.drawBitmap(overlay, null, new Rect(0, 0, size, size), null); michael@0: michael@0: // draw the favicon michael@0: if (aSource == null) michael@0: aSource = BitmapUtils.decodeResource(getContext(), R.drawable.home_star); michael@0: michael@0: // by default, we scale the icon to this size michael@0: int sWidth = insetSize / 2; michael@0: int sHeight = sWidth; michael@0: michael@0: int halfSize = size / 2; michael@0: canvas.drawBitmap(aSource, michael@0: null, michael@0: new Rect(halfSize - sWidth, michael@0: halfSize - sHeight, michael@0: halfSize + sWidth, michael@0: halfSize + sHeight), michael@0: null); michael@0: michael@0: return bitmap; michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetHandlersForMimeTypeWrapper") michael@0: static String[] getHandlersForMimeType(String aMimeType, String aAction) { michael@0: Intent intent = getIntentForActionString(aAction); michael@0: if (aMimeType != null && aMimeType.length() > 0) michael@0: intent.setType(aMimeType); michael@0: return getHandlersForIntent(intent); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetHandlersForURLWrapper") michael@0: static String[] getHandlersForURL(String aURL, String aAction) { michael@0: // aURL may contain the whole URL or just the protocol michael@0: Uri uri = aURL.indexOf(':') >= 0 ? Uri.parse(aURL) : new Uri.Builder().scheme(aURL).build(); michael@0: michael@0: Intent intent = getOpenURIIntent(getContext(), uri.toString(), "", michael@0: TextUtils.isEmpty(aAction) ? Intent.ACTION_VIEW : aAction, ""); michael@0: michael@0: return getHandlersForIntent(intent); michael@0: } michael@0: michael@0: static boolean hasHandlersForIntent(Intent intent) { michael@0: PackageManager pm = getContext().getPackageManager(); michael@0: List list = pm.queryIntentActivities(intent, 0); michael@0: return !list.isEmpty(); michael@0: } michael@0: michael@0: static String[] getHandlersForIntent(Intent intent) { michael@0: PackageManager pm = getContext().getPackageManager(); michael@0: List list = pm.queryIntentActivities(intent, 0); michael@0: int numAttr = 4; michael@0: String[] ret = new String[list.size() * numAttr]; michael@0: for (int i = 0; i < list.size(); i++) { michael@0: ResolveInfo resolveInfo = list.get(i); michael@0: ret[i * numAttr] = resolveInfo.loadLabel(pm).toString(); michael@0: if (resolveInfo.isDefault) michael@0: ret[i * numAttr + 1] = "default"; michael@0: else michael@0: ret[i * numAttr + 1] = ""; michael@0: ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName; michael@0: ret[i * numAttr + 3] = resolveInfo.activityInfo.name; michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: static Intent getIntentForActionString(String aAction) { michael@0: // Default to the view action if no other action as been specified. michael@0: if (TextUtils.isEmpty(aAction)) { michael@0: return new Intent(Intent.ACTION_VIEW); michael@0: } michael@0: return new Intent(aAction); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetExtensionFromMimeTypeWrapper") michael@0: static String getExtensionFromMimeType(String aMimeType) { michael@0: return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetMimeTypeFromExtensionsWrapper") michael@0: static String getMimeTypeFromExtensions(String aFileExt) { michael@0: StringTokenizer st = new StringTokenizer(aFileExt, ".,; "); michael@0: String type = null; michael@0: String subType = null; michael@0: while (st.hasMoreElements()) { michael@0: String ext = st.nextToken(); michael@0: String mt = getMimeTypeFromExtension(ext); michael@0: if (mt == null) michael@0: continue; michael@0: int slash = mt.indexOf('/'); michael@0: String tmpType = mt.substring(0, slash); michael@0: if (!tmpType.equalsIgnoreCase(type)) michael@0: type = type == null ? tmpType : "*"; michael@0: String tmpSubType = mt.substring(slash + 1); michael@0: if (!tmpSubType.equalsIgnoreCase(subType)) michael@0: subType = subType == null ? tmpSubType : "*"; michael@0: } michael@0: if (type == null) michael@0: type = "*"; michael@0: if (subType == null) michael@0: subType = "*"; michael@0: return type + "/" + subType; michael@0: } michael@0: michael@0: static void safeStreamClose(Closeable stream) { michael@0: try { michael@0: if (stream != null) michael@0: stream.close(); michael@0: } catch (IOException e) {} michael@0: } michael@0: michael@0: static boolean isUriSafeForScheme(Uri aUri) { michael@0: // Bug 794034 - We don't want to pass MWI or USSD codes to the michael@0: // dialer, and ensure the Uri class doesn't parse a URI michael@0: // containing a fragment ('#') michael@0: final String scheme = aUri.getScheme(); michael@0: if ("tel".equals(scheme) || "sms".equals(scheme)) { michael@0: final String number = aUri.getSchemeSpecificPart(); michael@0: if (number.contains("#") || number.contains("*") || aUri.getFragment() != null) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Given the inputs to getOpenURIIntent, plus an optional michael@0: * package name and class name, create and fire an intent to open the michael@0: * provided URI. If a class name is specified but a package name is not, michael@0: * we will default to using the current fennec package. michael@0: * michael@0: * @param targetURI the string spec of the URI to open. michael@0: * @param mimeType an optional MIME type string. michael@0: * @param packageName an optional app package name. michael@0: * @param className an optional intent class name. michael@0: * @param action an Android action specifier, such as michael@0: * Intent.ACTION_SEND. michael@0: * @param title the title to use in ACTION_SEND intents. michael@0: * @return true if the activity started successfully; false otherwise. michael@0: */ michael@0: @WrapElementForJNI michael@0: public static boolean openUriExternal(String targetURI, michael@0: String mimeType, michael@0: @OptionalGeneratedParameter String packageName, michael@0: @OptionalGeneratedParameter String className, michael@0: @OptionalGeneratedParameter String action, michael@0: @OptionalGeneratedParameter String title) { michael@0: final Context context = getContext(); michael@0: final Intent intent = getOpenURIIntent(context, targetURI, michael@0: mimeType, action, title); michael@0: michael@0: if (intent == null) { michael@0: return false; michael@0: } michael@0: michael@0: if (!TextUtils.isEmpty(className)) { michael@0: if (!TextUtils.isEmpty(packageName)) { michael@0: intent.setClassName(packageName, className); michael@0: } else { michael@0: // Default to using the fennec app context. michael@0: intent.setClassName(context, className); michael@0: } michael@0: } michael@0: michael@0: intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); michael@0: try { michael@0: context.startActivity(intent); michael@0: return true; michael@0: } catch (ActivityNotFoundException e) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Return a Uri instance which is equivalent to u, michael@0: * but with a guaranteed-lowercase scheme as if the API level 16 method michael@0: * u.normalizeScheme had been called. michael@0: * michael@0: * @param u the Uri to normalize. michael@0: * @return a Uri, which might be u. michael@0: */ michael@0: static Uri normalizeUriScheme(final Uri u) { michael@0: final String scheme = u.getScheme(); michael@0: final String lower = scheme.toLowerCase(Locale.US); michael@0: if (lower.equals(scheme)) { michael@0: return u; michael@0: } michael@0: michael@0: // Otherwise, return a new URI with a normalized scheme. michael@0: return u.buildUpon().scheme(lower).build(); michael@0: } michael@0: michael@0: /** michael@0: * Given a URI, a MIME type, and a title, michael@0: * produce a share intent which can be used to query all activities michael@0: * than can open the specified URI. michael@0: * michael@0: * @param context a Context instance. michael@0: * @param targetURI the string spec of the URI to open. michael@0: * @param mimeType an optional MIME type string. michael@0: * @param title the title to use in ACTION_SEND intents. michael@0: * @return an Intent, or null if none could be michael@0: * produced. michael@0: */ michael@0: public static Intent getShareIntent(final Context context, michael@0: final String targetURI, michael@0: final String mimeType, michael@0: final String title) { michael@0: Intent shareIntent = getIntentForActionString(Intent.ACTION_SEND); michael@0: shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI); michael@0: shareIntent.putExtra(Intent.EXTRA_SUBJECT, title); michael@0: michael@0: // Note that EXTRA_TITLE is intended to be used for share dialog michael@0: // titles. Common usage (e.g., Pocket) suggests that it's sometimes michael@0: // interpreted as an alternate to EXTRA_SUBJECT, so we include it. michael@0: shareIntent.putExtra(Intent.EXTRA_TITLE, title); michael@0: michael@0: if (mimeType != null && mimeType.length() > 0) { michael@0: shareIntent.setType(mimeType); michael@0: } michael@0: michael@0: return shareIntent; michael@0: } michael@0: michael@0: /** michael@0: * Given a URI, a MIME type, an Android intent "action", and a title, michael@0: * produce an intent which can be used to start an activity to open michael@0: * the specified URI. michael@0: * michael@0: * @param context a Context instance. michael@0: * @param targetURI the string spec of the URI to open. michael@0: * @param mimeType an optional MIME type string. michael@0: * @param action an Android action specifier, such as michael@0: * Intent.ACTION_SEND. michael@0: * @param title the title to use in ACTION_SEND intents. michael@0: * @return an Intent, or null if none could be michael@0: * produced. michael@0: */ michael@0: static Intent getOpenURIIntent(final Context context, michael@0: final String targetURI, michael@0: final String mimeType, michael@0: final String action, michael@0: final String title) { michael@0: michael@0: if (action.equalsIgnoreCase(Intent.ACTION_SEND)) { michael@0: Intent shareIntent = getShareIntent(context, targetURI, mimeType, title); michael@0: return Intent.createChooser(shareIntent, michael@0: context.getResources().getString(R.string.share_title)); michael@0: } michael@0: michael@0: final Uri uri = normalizeUriScheme(targetURI.indexOf(':') >= 0 ? Uri.parse(targetURI) : new Uri.Builder().scheme(targetURI).build()); michael@0: if (mimeType.length() > 0) { michael@0: Intent intent = getIntentForActionString(action); michael@0: intent.setDataAndType(uri, mimeType); michael@0: return intent; michael@0: } michael@0: michael@0: if (!isUriSafeForScheme(uri)) { michael@0: return null; michael@0: } michael@0: michael@0: final String scheme = uri.getScheme(); michael@0: michael@0: final Intent intent; michael@0: michael@0: // Compute our most likely intent, then check to see if there are any michael@0: // custom handlers that would apply. michael@0: // Start with the original URI. If we end up modifying it, we'll michael@0: // overwrite it. michael@0: final Intent likelyIntent = getIntentForActionString(action); michael@0: likelyIntent.setData(uri); michael@0: michael@0: if ("vnd.youtube".equals(scheme) && !hasHandlersForIntent(likelyIntent)) { michael@0: // Special-case YouTube to use our own player if no system handler michael@0: // exists. michael@0: intent = new Intent(VideoPlayer.VIDEO_ACTION); michael@0: intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, michael@0: "org.mozilla.gecko.VideoPlayer"); michael@0: intent.setData(uri); michael@0: } else { michael@0: intent = likelyIntent; michael@0: } michael@0: michael@0: // Have a special handling for SMS, as the message body michael@0: // is not extracted from the URI automatically. michael@0: if (!"sms".equals(scheme)) { michael@0: return intent; michael@0: } michael@0: michael@0: final String query = uri.getEncodedQuery(); michael@0: if (TextUtils.isEmpty(query)) { michael@0: return intent; michael@0: } michael@0: michael@0: final String[] fields = query.split("&"); michael@0: boolean foundBody = false; michael@0: String resultQuery = ""; michael@0: for (String field : fields) { michael@0: if (foundBody || !field.startsWith("body=")) { michael@0: resultQuery = resultQuery.concat(resultQuery.length() > 0 ? "&" + field : field); michael@0: continue; michael@0: } michael@0: michael@0: // Found the first body param. Put it into the intent. michael@0: final String body = Uri.decode(field.substring(5)); michael@0: intent.putExtra("sms_body", body); michael@0: foundBody = true; michael@0: } michael@0: michael@0: if (!foundBody) { michael@0: // No need to rewrite the URI, then. michael@0: return intent; michael@0: } michael@0: michael@0: // Form a new URI without the body field in the query part, and michael@0: // push that into the new Intent. michael@0: final String newQuery = resultQuery.length() > 0 ? "?" + resultQuery : ""; michael@0: final Uri pruned = uri.buildUpon().encodedQuery(newQuery).build(); michael@0: intent.setData(pruned); michael@0: michael@0: return intent; michael@0: } michael@0: michael@0: /** michael@0: * Only called from GeckoApp. michael@0: */ michael@0: public static void setNotificationClient(NotificationClient client) { michael@0: if (notificationClient == null) { michael@0: notificationClient = client; michael@0: } else { michael@0: Log.d(LOGTAG, "Notification client already set"); michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "ShowAlertNotificationWrapper") michael@0: public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText, michael@0: String aAlertCookie, String aAlertName) { michael@0: // The intent to launch when the user clicks the expanded notification michael@0: String app = getContext().getClass().getName(); michael@0: Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CALLBACK); michael@0: notificationIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, app); michael@0: notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); michael@0: michael@0: int notificationID = aAlertName.hashCode(); michael@0: michael@0: // Put the strings into the intent as an URI "alert:?name=&app=&cookie=" michael@0: Uri.Builder b = new Uri.Builder(); michael@0: Uri dataUri = b.scheme("alert").path(Integer.toString(notificationID)) michael@0: .appendQueryParameter("name", aAlertName) michael@0: .appendQueryParameter("cookie", aAlertCookie) michael@0: .build(); michael@0: notificationIntent.setData(dataUri); michael@0: PendingIntent contentIntent = PendingIntent.getActivity( michael@0: getContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); michael@0: michael@0: ALERT_COOKIES.put(aAlertName, aAlertCookie); michael@0: callObserver(aAlertName, "alertshow", aAlertCookie); michael@0: michael@0: notificationClient.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) { michael@0: int notificationID = aAlertName.hashCode(); michael@0: notificationClient.update(notificationID, aProgress, aProgressMax, aAlertText); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void closeNotification(String aAlertName) { michael@0: String alertCookie = ALERT_COOKIES.get(aAlertName); michael@0: if (alertCookie != null) { michael@0: callObserver(aAlertName, "alertfinished", alertCookie); michael@0: ALERT_COOKIES.remove(aAlertName); michael@0: } michael@0: michael@0: removeObserver(aAlertName); michael@0: michael@0: int notificationID = aAlertName.hashCode(); michael@0: notificationClient.remove(notificationID); michael@0: } michael@0: michael@0: public static void handleNotification(String aAction, String aAlertName, String aAlertCookie) { michael@0: int notificationID = aAlertName.hashCode(); michael@0: michael@0: if (GeckoApp.ACTION_ALERT_CALLBACK.equals(aAction)) { michael@0: callObserver(aAlertName, "alertclickcallback", aAlertCookie); michael@0: michael@0: if (notificationClient.isOngoing(notificationID)) { michael@0: // When clicked, keep the notification if it displays progress michael@0: return; michael@0: } michael@0: } michael@0: closeNotification(aAlertName); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetDpiWrapper") michael@0: public static int getDpi() { michael@0: if (sDensityDpi == 0) { michael@0: sDensityDpi = getContext().getResources().getDisplayMetrics().densityDpi; michael@0: } michael@0: michael@0: return sDensityDpi; michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static float getDensity() { michael@0: return getContext().getResources().getDisplayMetrics().density; michael@0: } michael@0: michael@0: private static boolean isHighMemoryDevice() { michael@0: return HardwareUtils.getMemSize() > HIGH_MEMORY_DEVICE_THRESHOLD_MB; michael@0: } michael@0: michael@0: /** michael@0: * Returns the colour depth of the default screen. This will either be michael@0: * 24 or 16. michael@0: */ michael@0: @WrapElementForJNI(stubName = "GetScreenDepthWrapper") michael@0: public static synchronized int getScreenDepth() { michael@0: if (sScreenDepth == 0) { michael@0: sScreenDepth = 16; michael@0: PixelFormat info = new PixelFormat(); michael@0: PixelFormat.getPixelFormatInfo(getGeckoInterface().getActivity().getWindowManager().getDefaultDisplay().getPixelFormat(), info); michael@0: if (info.bitsPerPixel >= 24 && isHighMemoryDevice()) { michael@0: sScreenDepth = 24; michael@0: } michael@0: } michael@0: michael@0: return sScreenDepth; michael@0: } michael@0: michael@0: public static synchronized void setScreenDepthOverride(int aScreenDepth) { michael@0: if (sScreenDepth != 0) { michael@0: Log.e(LOGTAG, "Tried to override screen depth after it's already been set"); michael@0: return; michael@0: } michael@0: michael@0: sScreenDepth = aScreenDepth; michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void setFullScreen(boolean fullscreen) { michael@0: if (getGeckoInterface() != null) michael@0: getGeckoInterface().setFullScreen(fullscreen); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void performHapticFeedback(boolean aIsLongPress) { michael@0: // Don't perform haptic feedback if a vibration is currently playing, michael@0: // because the haptic feedback will nuke the vibration. michael@0: if (!sVibrationMaybePlaying || System.nanoTime() >= sVibrationEndTime) { michael@0: LayerView layerView = getLayerView(); michael@0: layerView.performHapticFeedback(aIsLongPress ? michael@0: HapticFeedbackConstants.LONG_PRESS : michael@0: HapticFeedbackConstants.VIRTUAL_KEY); michael@0: } michael@0: } michael@0: michael@0: private static Vibrator vibrator() { michael@0: LayerView layerView = getLayerView(); michael@0: return (Vibrator) layerView.getContext().getSystemService(Context.VIBRATOR_SERVICE); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "Vibrate1") michael@0: public static void vibrate(long milliseconds) { michael@0: sVibrationEndTime = System.nanoTime() + milliseconds * 1000000; michael@0: sVibrationMaybePlaying = true; michael@0: vibrator().vibrate(milliseconds); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "VibrateA") michael@0: public static void vibrate(long[] pattern, int repeat) { michael@0: // If pattern.length is even, the last element in the pattern is a michael@0: // meaningless delay, so don't include it in vibrationDuration. michael@0: long vibrationDuration = 0; michael@0: int iterLen = pattern.length - (pattern.length % 2 == 0 ? 1 : 0); michael@0: for (int i = 0; i < iterLen; i++) { michael@0: vibrationDuration += pattern[i]; michael@0: } michael@0: michael@0: sVibrationEndTime = System.nanoTime() + vibrationDuration * 1000000; michael@0: sVibrationMaybePlaying = true; michael@0: vibrator().vibrate(pattern, repeat); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void cancelVibrate() { michael@0: sVibrationMaybePlaying = false; michael@0: sVibrationEndTime = 0; michael@0: vibrator().cancel(); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void showInputMethodPicker() { michael@0: InputMethodManager imm = (InputMethodManager) michael@0: getContext().getSystemService(Context.INPUT_METHOD_SERVICE); michael@0: imm.showInputMethodPicker(); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void setKeepScreenOn(final boolean on) { michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: // TODO michael@0: } michael@0: }); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void notifyDefaultPrevented(final boolean defaultPrevented) { michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: LayerView view = getLayerView(); michael@0: PanZoomController controller = (view == null ? null : view.getPanZoomController()); michael@0: if (controller != null) { michael@0: controller.notifyDefaultActionPrevented(defaultPrevented); michael@0: } michael@0: } michael@0: }); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static boolean isNetworkLinkUp() { michael@0: ConnectivityManager cm = (ConnectivityManager) michael@0: getContext().getSystemService(Context.CONNECTIVITY_SERVICE); michael@0: try { michael@0: NetworkInfo info = cm.getActiveNetworkInfo(); michael@0: if (info == null || !info.isConnected()) michael@0: return false; michael@0: } catch (SecurityException se) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static boolean isNetworkLinkKnown() { michael@0: ConnectivityManager cm = (ConnectivityManager) michael@0: getContext().getSystemService(Context.CONNECTIVITY_SERVICE); michael@0: try { michael@0: if (cm.getActiveNetworkInfo() == null) michael@0: return false; michael@0: } catch (SecurityException se) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static int networkLinkType() { michael@0: ConnectivityManager cm = (ConnectivityManager) michael@0: getContext().getSystemService(Context.CONNECTIVITY_SERVICE); michael@0: NetworkInfo info = cm.getActiveNetworkInfo(); michael@0: if (info == null) { michael@0: return LINK_TYPE_UNKNOWN; michael@0: } michael@0: michael@0: switch (info.getType()) { michael@0: case ConnectivityManager.TYPE_ETHERNET: michael@0: return LINK_TYPE_ETHERNET; michael@0: case ConnectivityManager.TYPE_WIFI: michael@0: return LINK_TYPE_WIFI; michael@0: case ConnectivityManager.TYPE_WIMAX: michael@0: return LINK_TYPE_WIMAX; michael@0: case ConnectivityManager.TYPE_MOBILE: michael@0: break; // We will handle sub-types after the switch. michael@0: default: michael@0: Log.w(LOGTAG, "Ignoring the current network type."); michael@0: return LINK_TYPE_UNKNOWN; michael@0: } michael@0: michael@0: TelephonyManager tm = (TelephonyManager) michael@0: getContext().getSystemService(Context.TELEPHONY_SERVICE); michael@0: if (tm == null) { michael@0: Log.e(LOGTAG, "Telephony service does not exist"); michael@0: return LINK_TYPE_UNKNOWN; michael@0: } michael@0: michael@0: switch (tm.getNetworkType()) { michael@0: case TelephonyManager.NETWORK_TYPE_IDEN: michael@0: case TelephonyManager.NETWORK_TYPE_CDMA: michael@0: case TelephonyManager.NETWORK_TYPE_GPRS: michael@0: return LINK_TYPE_2G; michael@0: case TelephonyManager.NETWORK_TYPE_1xRTT: michael@0: case TelephonyManager.NETWORK_TYPE_EDGE: michael@0: return LINK_TYPE_2G; // 2.5G michael@0: case TelephonyManager.NETWORK_TYPE_UMTS: michael@0: case TelephonyManager.NETWORK_TYPE_EVDO_0: michael@0: return LINK_TYPE_3G; michael@0: case TelephonyManager.NETWORK_TYPE_HSPA: michael@0: case TelephonyManager.NETWORK_TYPE_HSDPA: michael@0: case TelephonyManager.NETWORK_TYPE_HSUPA: michael@0: case TelephonyManager.NETWORK_TYPE_EVDO_A: michael@0: case TelephonyManager.NETWORK_TYPE_EVDO_B: michael@0: case TelephonyManager.NETWORK_TYPE_EHRPD: michael@0: return LINK_TYPE_3G; // 3.5G michael@0: case TelephonyManager.NETWORK_TYPE_HSPAP: michael@0: return LINK_TYPE_3G; // 3.75G michael@0: case TelephonyManager.NETWORK_TYPE_LTE: michael@0: return LINK_TYPE_4G; // 3.9G michael@0: case TelephonyManager.NETWORK_TYPE_UNKNOWN: michael@0: default: michael@0: Log.w(LOGTAG, "Connected to an unknown mobile network!"); michael@0: return LINK_TYPE_UNKNOWN; michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetSystemColoursWrapper") michael@0: public static int[] getSystemColors() { michael@0: // attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h michael@0: final int[] attrsAppearance = { michael@0: android.R.attr.textColor, michael@0: android.R.attr.textColorPrimary, michael@0: android.R.attr.textColorPrimaryInverse, michael@0: android.R.attr.textColorSecondary, michael@0: android.R.attr.textColorSecondaryInverse, michael@0: android.R.attr.textColorTertiary, michael@0: android.R.attr.textColorTertiaryInverse, michael@0: android.R.attr.textColorHighlight, michael@0: android.R.attr.colorForeground, michael@0: android.R.attr.colorBackground, michael@0: android.R.attr.panelColorForeground, michael@0: android.R.attr.panelColorBackground michael@0: }; michael@0: michael@0: int[] result = new int[attrsAppearance.length]; michael@0: michael@0: final ContextThemeWrapper contextThemeWrapper = michael@0: new ContextThemeWrapper(getContext(), android.R.style.TextAppearance); michael@0: michael@0: final TypedArray appearance = contextThemeWrapper.getTheme().obtainStyledAttributes(attrsAppearance); michael@0: michael@0: if (appearance != null) { michael@0: for (int i = 0; i < appearance.getIndexCount(); i++) { michael@0: int idx = appearance.getIndex(i); michael@0: int color = appearance.getColor(idx, 0); michael@0: result[idx] = color; michael@0: } michael@0: appearance.recycle(); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void killAnyZombies() { michael@0: GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() { michael@0: @Override michael@0: public boolean callback(int pid) { michael@0: if (pid != android.os.Process.myPid()) michael@0: android.os.Process.killProcess(pid); michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: EnumerateGeckoProcesses(visitor); michael@0: } michael@0: michael@0: public static boolean checkForGeckoProcs() { michael@0: michael@0: class GeckoPidCallback implements GeckoProcessesVisitor { michael@0: public boolean otherPidExist = false; michael@0: @Override michael@0: public boolean callback(int pid) { michael@0: if (pid != android.os.Process.myPid()) { michael@0: otherPidExist = true; michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: GeckoPidCallback visitor = new GeckoPidCallback(); michael@0: EnumerateGeckoProcesses(visitor); michael@0: return visitor.otherPidExist; michael@0: } michael@0: michael@0: interface GeckoProcessesVisitor{ michael@0: boolean callback(int pid); michael@0: } michael@0: michael@0: private static void EnumerateGeckoProcesses(GeckoProcessesVisitor visiter) { michael@0: int pidColumn = -1; michael@0: int userColumn = -1; michael@0: michael@0: try { michael@0: // run ps and parse its output michael@0: java.lang.Process ps = Runtime.getRuntime().exec("ps"); michael@0: BufferedReader in = new BufferedReader(new InputStreamReader(ps.getInputStream()), michael@0: 2048); michael@0: michael@0: String headerOutput = in.readLine(); michael@0: michael@0: // figure out the column offsets. We only care about the pid and user fields michael@0: StringTokenizer st = new StringTokenizer(headerOutput); michael@0: michael@0: int tokenSoFar = 0; michael@0: while (st.hasMoreTokens()) { michael@0: String next = st.nextToken(); michael@0: if (next.equalsIgnoreCase("PID")) michael@0: pidColumn = tokenSoFar; michael@0: else if (next.equalsIgnoreCase("USER")) michael@0: userColumn = tokenSoFar; michael@0: tokenSoFar++; michael@0: } michael@0: michael@0: // alright, the rest are process entries. michael@0: String psOutput = null; michael@0: while ((psOutput = in.readLine()) != null) { michael@0: String[] split = psOutput.split("\\s+"); michael@0: if (split.length <= pidColumn || split.length <= userColumn) michael@0: continue; michael@0: int uid = android.os.Process.getUidForName(split[userColumn]); michael@0: if (uid == android.os.Process.myUid() && michael@0: !split[split.length - 1].equalsIgnoreCase("ps")) { michael@0: int pid = Integer.parseInt(split[pidColumn]); michael@0: boolean keepGoing = visiter.callback(pid); michael@0: if (keepGoing == false) michael@0: break; michael@0: } michael@0: } michael@0: in.close(); michael@0: } michael@0: catch (Exception e) { michael@0: Log.w(LOGTAG, "Failed to enumerate Gecko processes.", e); michael@0: } michael@0: } michael@0: michael@0: public static void waitForAnotherGeckoProc(){ michael@0: int countdown = 40; michael@0: while (!checkForGeckoProcs() && --countdown > 0) { michael@0: try { michael@0: Thread.sleep(100); michael@0: } catch (InterruptedException ie) {} michael@0: } michael@0: } michael@0: public static String getAppNameByPID(int pid) { michael@0: BufferedReader cmdlineReader = null; michael@0: String path = "/proc/" + pid + "/cmdline"; michael@0: try { michael@0: File cmdlineFile = new File(path); michael@0: if (!cmdlineFile.exists()) michael@0: return ""; michael@0: cmdlineReader = new BufferedReader(new FileReader(cmdlineFile)); michael@0: return cmdlineReader.readLine().trim(); michael@0: } catch (Exception ex) { michael@0: return ""; michael@0: } finally { michael@0: if (null != cmdlineReader) { michael@0: try { michael@0: cmdlineReader.close(); michael@0: } catch (Exception e) {} michael@0: } michael@0: } michael@0: } michael@0: michael@0: public static void listOfOpenFiles() { michael@0: int pidColumn = -1; michael@0: int nameColumn = -1; michael@0: michael@0: try { michael@0: String filter = GeckoProfile.get(getContext()).getDir().toString(); michael@0: Log.i(LOGTAG, "[OPENFILE] Filter: " + filter); michael@0: michael@0: // run lsof and parse its output michael@0: java.lang.Process lsof = Runtime.getRuntime().exec("lsof"); michael@0: BufferedReader in = new BufferedReader(new InputStreamReader(lsof.getInputStream()), 2048); michael@0: michael@0: String headerOutput = in.readLine(); michael@0: StringTokenizer st = new StringTokenizer(headerOutput); michael@0: int token = 0; michael@0: while (st.hasMoreTokens()) { michael@0: String next = st.nextToken(); michael@0: if (next.equalsIgnoreCase("PID")) michael@0: pidColumn = token; michael@0: else if (next.equalsIgnoreCase("NAME")) michael@0: nameColumn = token; michael@0: token++; michael@0: } michael@0: michael@0: // alright, the rest are open file entries. michael@0: Map pidNameMap = new TreeMap(); michael@0: String output = null; michael@0: while ((output = in.readLine()) != null) { michael@0: String[] split = output.split("\\s+"); michael@0: if (split.length <= pidColumn || split.length <= nameColumn) michael@0: continue; michael@0: Integer pid = new Integer(split[pidColumn]); michael@0: String name = pidNameMap.get(pid); michael@0: if (name == null) { michael@0: name = getAppNameByPID(pid.intValue()); michael@0: pidNameMap.put(pid, name); michael@0: } michael@0: String file = split[nameColumn]; michael@0: if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(file) && file.startsWith(filter)) michael@0: Log.i(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file); michael@0: } michael@0: in.close(); michael@0: } catch (Exception e) { } michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void scanMedia(String aFile, String aMimeType) { michael@0: // If the platform didn't give us a mimetype, try to guess one from the filename michael@0: if (TextUtils.isEmpty(aMimeType)) { michael@0: int extPosition = aFile.lastIndexOf("."); michael@0: if (extPosition > 0 && extPosition < aFile.length() - 1) { michael@0: aMimeType = getMimeTypeFromExtension(aFile.substring(extPosition+1)); michael@0: } michael@0: } michael@0: michael@0: Context context = getContext(); michael@0: GeckoMediaScannerClient.startScan(context, aFile, aMimeType); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetIconForExtensionWrapper") michael@0: public static byte[] getIconForExtension(String aExt, int iconSize) { michael@0: try { michael@0: if (iconSize <= 0) michael@0: iconSize = 16; michael@0: michael@0: if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.') michael@0: aExt = aExt.substring(1); michael@0: michael@0: PackageManager pm = getContext().getPackageManager(); michael@0: Drawable icon = getDrawableForExtension(pm, aExt); michael@0: if (icon == null) { michael@0: // Use a generic icon michael@0: icon = pm.getDefaultActivityIcon(); michael@0: } michael@0: michael@0: Bitmap bitmap = ((BitmapDrawable)icon).getBitmap(); michael@0: if (bitmap.getWidth() != iconSize || bitmap.getHeight() != iconSize) michael@0: bitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true); michael@0: michael@0: ByteBuffer buf = ByteBuffer.allocate(iconSize * iconSize * 4); michael@0: bitmap.copyPixelsToBuffer(buf); michael@0: michael@0: return buf.array(); michael@0: } michael@0: catch (Exception e) { michael@0: Log.w(LOGTAG, "getIconForExtension failed.", e); michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: private static String getMimeTypeFromExtension(String ext) { michael@0: final MimeTypeMap mtm = MimeTypeMap.getSingleton(); michael@0: return mtm.getMimeTypeFromExtension(ext); michael@0: } michael@0: michael@0: private static Drawable getDrawableForExtension(PackageManager pm, String aExt) { michael@0: Intent intent = new Intent(Intent.ACTION_VIEW); michael@0: final String mimeType = getMimeTypeFromExtension(aExt); michael@0: if (mimeType != null && mimeType.length() > 0) michael@0: intent.setType(mimeType); michael@0: else michael@0: return null; michael@0: michael@0: List list = pm.queryIntentActivities(intent, 0); michael@0: if (list.size() == 0) michael@0: return null; michael@0: michael@0: ResolveInfo resolveInfo = list.get(0); michael@0: michael@0: if (resolveInfo == null) michael@0: return null; michael@0: michael@0: ActivityInfo activityInfo = resolveInfo.activityInfo; michael@0: michael@0: return activityInfo.loadIcon(pm); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static boolean getShowPasswordSetting() { michael@0: try { michael@0: int showPassword = michael@0: Settings.System.getInt(getContext().getContentResolver(), michael@0: Settings.System.TEXT_SHOW_PASSWORD, 1); michael@0: return (showPassword > 0); michael@0: } michael@0: catch (Exception e) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "AddPluginViewWrapper") michael@0: public static void addPluginView(View view, michael@0: float x, float y, michael@0: float w, float h, michael@0: boolean isFullScreen) { michael@0: if (getGeckoInterface() != null) michael@0: getGeckoInterface().addPluginView(view, new RectF(x, y, x + w, y + h), isFullScreen); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void removePluginView(View view, boolean isFullScreen) { michael@0: if (getGeckoInterface() != null) michael@0: getGeckoInterface().removePluginView(view, isFullScreen); michael@0: } michael@0: michael@0: /** michael@0: * A plugin that wish to be loaded in the WebView must provide this permission michael@0: * in their AndroidManifest.xml. michael@0: */ michael@0: public static final String PLUGIN_ACTION = "android.webkit.PLUGIN"; michael@0: public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN"; michael@0: michael@0: private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/"; michael@0: michael@0: private static final String PLUGIN_TYPE = "type"; michael@0: private static final String TYPE_NATIVE = "native"; michael@0: static public ArrayList mPackageInfoCache = new ArrayList(); michael@0: michael@0: // Returns null if plugins are blocked on the device. michael@0: static String[] getPluginDirectories() { michael@0: michael@0: // An awful hack to detect Tegra devices. Easiest way to do it without spinning up a EGL context. michael@0: boolean isTegra = (new File("/system/lib/hw/gralloc.tegra.so")).exists() || michael@0: (new File("/system/lib/hw/gralloc.tegra3.so")).exists(); michael@0: if (isTegra) { michael@0: // disable Flash on Tegra ICS with CM9 and other custom firmware (bug 736421) michael@0: File vfile = new File("/proc/version"); michael@0: FileReader vreader = null; michael@0: try { michael@0: if (vfile.canRead()) { michael@0: vreader = new FileReader(vfile); michael@0: String version = new BufferedReader(vreader).readLine(); michael@0: if (version.indexOf("CM9") != -1 || michael@0: version.indexOf("cyanogen") != -1 || michael@0: version.indexOf("Nova") != -1) michael@0: { michael@0: Log.w(LOGTAG, "Blocking plugins because of Tegra 2 + unofficial ICS bug (bug 736421)"); michael@0: return null; michael@0: } michael@0: } michael@0: } catch (IOException ex) { michael@0: // nothing michael@0: } finally { michael@0: try { michael@0: if (vreader != null) { michael@0: vreader.close(); michael@0: } michael@0: } catch (IOException ex) { michael@0: // nothing michael@0: } michael@0: } michael@0: michael@0: // disable on KitKat (bug 957694) michael@0: if (Build.VERSION.SDK_INT >= 19) { michael@0: Log.w(LOGTAG, "Blocking plugins because of Tegra (bug 957694)"); michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: ArrayList directories = new ArrayList(); michael@0: PackageManager pm = getContext().getPackageManager(); michael@0: List plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION), michael@0: PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); michael@0: michael@0: synchronized(mPackageInfoCache) { michael@0: michael@0: // clear the list of existing packageInfo objects michael@0: mPackageInfoCache.clear(); michael@0: michael@0: michael@0: for (ResolveInfo info : plugins) { michael@0: michael@0: // retrieve the plugin's service information michael@0: ServiceInfo serviceInfo = info.serviceInfo; michael@0: if (serviceInfo == null) { michael@0: Log.w(LOGTAG, "Ignoring bad plugin."); michael@0: continue; michael@0: } michael@0: michael@0: // Blacklist HTC's flash lite. michael@0: // See bug #704516 - We're not quite sure what Flash Lite does, michael@0: // but loading it causes Flash to give errors and fail to draw. michael@0: if (serviceInfo.packageName.equals("com.htc.flashliteplugin")) { michael@0: Log.w(LOGTAG, "Skipping HTC's flash lite plugin"); michael@0: continue; michael@0: } michael@0: michael@0: michael@0: // Retrieve information from the plugin's manifest. michael@0: PackageInfo pkgInfo; michael@0: try { michael@0: pkgInfo = pm.getPackageInfo(serviceInfo.packageName, michael@0: PackageManager.GET_PERMISSIONS michael@0: | PackageManager.GET_SIGNATURES); michael@0: } catch (Exception e) { michael@0: Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName); michael@0: continue; michael@0: } michael@0: michael@0: if (pkgInfo == null) { michael@0: Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Could not load package information."); michael@0: continue; michael@0: } michael@0: michael@0: /* michael@0: * find the location of the plugin's shared library. The default michael@0: * is to assume the app is either a user installed app or an michael@0: * updated system app. In both of these cases the library is michael@0: * stored in the app's data directory. michael@0: */ michael@0: String directory = pkgInfo.applicationInfo.dataDir + "/lib"; michael@0: final int appFlags = pkgInfo.applicationInfo.flags; michael@0: final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM | michael@0: ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; michael@0: michael@0: // preloaded system app with no user updates michael@0: if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) { michael@0: directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName; michael@0: } michael@0: michael@0: // check if the plugin has the required permissions michael@0: String permissions[] = pkgInfo.requestedPermissions; michael@0: if (permissions == null) { michael@0: Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission."); michael@0: continue; michael@0: } michael@0: boolean permissionOk = false; michael@0: for (String permit : permissions) { michael@0: if (PLUGIN_PERMISSION.equals(permit)) { michael@0: permissionOk = true; michael@0: break; michael@0: } michael@0: } michael@0: if (!permissionOk) { michael@0: Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission (2)."); michael@0: continue; michael@0: } michael@0: michael@0: // check to ensure the plugin is properly signed michael@0: Signature signatures[] = pkgInfo.signatures; michael@0: if (signatures == null) { michael@0: Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Not signed."); michael@0: continue; michael@0: } michael@0: michael@0: // determine the type of plugin from the manifest michael@0: if (serviceInfo.metaData == null) { michael@0: Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no defined type."); michael@0: continue; michael@0: } michael@0: michael@0: String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE); michael@0: if (!TYPE_NATIVE.equals(pluginType)) { michael@0: Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType); michael@0: continue; michael@0: } michael@0: michael@0: try { michael@0: Class cls = getPluginClass(serviceInfo.packageName, serviceInfo.name); michael@0: michael@0: //TODO implement any requirements of the plugin class here! michael@0: boolean classFound = true; michael@0: michael@0: if (!classFound) { michael@0: Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class."); michael@0: continue; michael@0: } michael@0: michael@0: } catch (NameNotFoundException e) { michael@0: Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName); michael@0: continue; michael@0: } catch (ClassNotFoundException e) { michael@0: Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name); michael@0: continue; michael@0: } michael@0: michael@0: // if all checks have passed then make the plugin available michael@0: mPackageInfoCache.add(pkgInfo); michael@0: directories.add(directory); michael@0: } michael@0: } michael@0: michael@0: return directories.toArray(new String[directories.size()]); michael@0: } michael@0: michael@0: static String getPluginPackage(String pluginLib) { michael@0: michael@0: if (pluginLib == null || pluginLib.length() == 0) { michael@0: return null; michael@0: } michael@0: michael@0: synchronized(mPackageInfoCache) { michael@0: for (PackageInfo pkgInfo : mPackageInfoCache) { michael@0: if (pluginLib.contains(pkgInfo.packageName)) { michael@0: return pkgInfo.packageName; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return null; michael@0: } michael@0: michael@0: static Class getPluginClass(String packageName, String className) michael@0: throws NameNotFoundException, ClassNotFoundException { michael@0: Context pluginContext = getContext().createPackageContext(packageName, michael@0: Context.CONTEXT_INCLUDE_CODE | michael@0: Context.CONTEXT_IGNORE_SECURITY); michael@0: ClassLoader pluginCL = pluginContext.getClassLoader(); michael@0: return pluginCL.loadClass(className); michael@0: } michael@0: michael@0: @WrapElementForJNI(allowMultithread = true) michael@0: public static Class loadPluginClass(String className, String libName) { michael@0: if (getGeckoInterface() == null) michael@0: return null; michael@0: try { michael@0: final String packageName = getPluginPackage(libName); michael@0: final int contextFlags = Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY; michael@0: final Context pluginContext = getContext().createPackageContext(packageName, contextFlags); michael@0: return pluginContext.getClassLoader().loadClass(className); michael@0: } catch (java.lang.ClassNotFoundException cnfe) { michael@0: Log.w(LOGTAG, "Couldn't find plugin class " + className, cnfe); michael@0: return null; michael@0: } catch (android.content.pm.PackageManager.NameNotFoundException nnfe) { michael@0: Log.w(LOGTAG, "Couldn't find package.", nnfe); michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: private static ContextGetter sContextGetter; michael@0: michael@0: @WrapElementForJNI(allowMultithread = true) michael@0: public static Context getContext() { michael@0: return sContextGetter.getContext(); michael@0: } michael@0: michael@0: public static void setContextGetter(ContextGetter cg) { michael@0: sContextGetter = cg; michael@0: } michael@0: michael@0: public static SharedPreferences getSharedPreferences() { michael@0: if (sContextGetter == null) { michael@0: throw new IllegalStateException("No ContextGetter; cannot fetch prefs."); michael@0: } michael@0: return sContextGetter.getSharedPreferences(); michael@0: } michael@0: michael@0: public interface AppStateListener { michael@0: public void onPause(); michael@0: public void onResume(); michael@0: public void onOrientationChanged(); michael@0: } michael@0: michael@0: public interface GeckoInterface { michael@0: public GeckoProfile getProfile(); michael@0: public PromptService getPromptService(); michael@0: public Activity getActivity(); michael@0: public String getDefaultUAString(); michael@0: public LocationListener getLocationListener(); michael@0: public SensorEventListener getSensorEventListener(); michael@0: public void doRestart(); michael@0: public void setFullScreen(boolean fullscreen); michael@0: public void addPluginView(View view, final RectF rect, final boolean isFullScreen); michael@0: public void removePluginView(final View view, final boolean isFullScreen); michael@0: public void enableCameraView(); michael@0: public void disableCameraView(); michael@0: public void addAppStateListener(AppStateListener listener); michael@0: public void removeAppStateListener(AppStateListener listener); michael@0: public View getCameraView(); michael@0: public void notifyWakeLockChanged(String topic, String state); michael@0: public FormAssistPopup getFormAssistPopup(); michael@0: public boolean areTabsShown(); michael@0: public AbsoluteLayout getPluginContainer(); michael@0: public void notifyCheckUpdateResult(String result); michael@0: public boolean hasTabsSideBar(); michael@0: public void invalidateOptionsMenu(); michael@0: }; michael@0: michael@0: private static GeckoInterface sGeckoInterface; michael@0: michael@0: public static GeckoInterface getGeckoInterface() { michael@0: return sGeckoInterface; michael@0: } michael@0: michael@0: public static void setGeckoInterface(GeckoInterface aGeckoInterface) { michael@0: sGeckoInterface = aGeckoInterface; michael@0: } michael@0: michael@0: public static android.hardware.Camera sCamera = null; michael@0: michael@0: static native void cameraCallbackBridge(byte[] data); michael@0: michael@0: static int kPreferedFps = 25; michael@0: static byte[] sCameraBuffer = null; michael@0: michael@0: michael@0: @WrapElementForJNI(stubName = "InitCameraWrapper") michael@0: static int[] initCamera(String aContentType, int aCamera, int aWidth, int aHeight) { michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: if (getGeckoInterface() != null) michael@0: getGeckoInterface().enableCameraView(); michael@0: } catch (Exception e) {} michael@0: } michael@0: }); michael@0: michael@0: // [0] = 0|1 (failure/success) michael@0: // [1] = width michael@0: // [2] = height michael@0: // [3] = fps michael@0: int[] result = new int[4]; michael@0: result[0] = 0; michael@0: michael@0: if (Build.VERSION.SDK_INT >= 9) { michael@0: if (android.hardware.Camera.getNumberOfCameras() == 0) michael@0: return result; michael@0: } michael@0: michael@0: try { michael@0: // no front/back camera before API level 9 michael@0: if (Build.VERSION.SDK_INT >= 9) michael@0: sCamera = android.hardware.Camera.open(aCamera); michael@0: else michael@0: sCamera = android.hardware.Camera.open(); michael@0: michael@0: android.hardware.Camera.Parameters params = sCamera.getParameters(); michael@0: params.setPreviewFormat(ImageFormat.NV21); michael@0: michael@0: // use the preview fps closest to 25 fps. michael@0: int fpsDelta = 1000; michael@0: try { michael@0: Iterator it = params.getSupportedPreviewFrameRates().iterator(); michael@0: while (it.hasNext()) { michael@0: int nFps = it.next(); michael@0: if (Math.abs(nFps - kPreferedFps) < fpsDelta) { michael@0: fpsDelta = Math.abs(nFps - kPreferedFps); michael@0: params.setPreviewFrameRate(nFps); michael@0: } michael@0: } michael@0: } catch(Exception e) { michael@0: params.setPreviewFrameRate(kPreferedFps); michael@0: } michael@0: michael@0: // set up the closest preview size available michael@0: Iterator sit = params.getSupportedPreviewSizes().iterator(); michael@0: int sizeDelta = 10000000; michael@0: int bufferSize = 0; michael@0: while (sit.hasNext()) { michael@0: android.hardware.Camera.Size size = sit.next(); michael@0: if (Math.abs(size.width * size.height - aWidth * aHeight) < sizeDelta) { michael@0: sizeDelta = Math.abs(size.width * size.height - aWidth * aHeight); michael@0: params.setPreviewSize(size.width, size.height); michael@0: bufferSize = size.width * size.height; michael@0: } michael@0: } michael@0: michael@0: try { michael@0: if (getGeckoInterface() != null) { michael@0: View cameraView = getGeckoInterface().getCameraView(); michael@0: if (cameraView instanceof SurfaceView) { michael@0: sCamera.setPreviewDisplay(((SurfaceView)cameraView).getHolder()); michael@0: } else if (cameraView instanceof TextureView) { michael@0: sCamera.setPreviewTexture(((TextureView)cameraView).getSurfaceTexture()); michael@0: } michael@0: } michael@0: } catch(IOException e) { michael@0: Log.w(LOGTAG, "Error setPreviewXXX:", e); michael@0: } catch(RuntimeException e) { michael@0: Log.w(LOGTAG, "Error setPreviewXXX:", e); michael@0: } michael@0: michael@0: sCamera.setParameters(params); michael@0: sCameraBuffer = new byte[(bufferSize * 12) / 8]; michael@0: sCamera.addCallbackBuffer(sCameraBuffer); michael@0: sCamera.setPreviewCallbackWithBuffer(new android.hardware.Camera.PreviewCallback() { michael@0: @Override michael@0: public void onPreviewFrame(byte[] data, android.hardware.Camera camera) { michael@0: cameraCallbackBridge(data); michael@0: if (sCamera != null) michael@0: sCamera.addCallbackBuffer(sCameraBuffer); michael@0: } michael@0: }); michael@0: sCamera.startPreview(); michael@0: params = sCamera.getParameters(); michael@0: result[0] = 1; michael@0: result[1] = params.getPreviewSize().width; michael@0: result[2] = params.getPreviewSize().height; michael@0: result[3] = params.getPreviewFrameRate(); michael@0: } catch(RuntimeException e) { michael@0: Log.w(LOGTAG, "initCamera RuntimeException.", e); michael@0: result[0] = result[1] = result[2] = result[3] = 0; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: static synchronized void closeCamera() { michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: if (getGeckoInterface() != null) michael@0: getGeckoInterface().disableCameraView(); michael@0: } catch (Exception e) {} michael@0: } michael@0: }); michael@0: if (sCamera != null) { michael@0: sCamera.stopPreview(); michael@0: sCamera.release(); michael@0: sCamera = null; michael@0: sCameraBuffer = null; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Adds a listener for a gecko event. michael@0: * This method is thread-safe and may be called at any time. In particular, calling it michael@0: * with an event that is currently being processed has the properly-defined behaviour that michael@0: * any added listeners will not be invoked on the event currently being processed, but michael@0: * will be invoked on future events of that type. michael@0: */ michael@0: @RobocopTarget michael@0: public static void registerEventListener(String event, GeckoEventListener listener) { michael@0: sEventDispatcher.registerEventListener(event, listener); michael@0: } michael@0: michael@0: public static EventDispatcher getEventDispatcher() { michael@0: return sEventDispatcher; michael@0: } michael@0: michael@0: /** michael@0: * Remove a previously-registered listener for a gecko event. michael@0: * This method is thread-safe and may be called at any time. In particular, calling it michael@0: * with an event that is currently being processed has the properly-defined behaviour that michael@0: * any removed listeners will still be invoked on the event currently being processed, but michael@0: * will not be invoked on future events of that type. michael@0: */ michael@0: @RobocopTarget michael@0: public static void unregisterEventListener(String event, GeckoEventListener listener) { michael@0: sEventDispatcher.unregisterEventListener(event, listener); michael@0: } michael@0: michael@0: /* michael@0: * Battery API related methods. michael@0: */ michael@0: @WrapElementForJNI michael@0: public static void enableBatteryNotifications() { michael@0: GeckoBatteryManager.enableNotifications(); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "HandleGeckoMessageWrapper") michael@0: public static void handleGeckoMessage(final NativeJSContainer message) { michael@0: sEventDispatcher.dispatchEvent(message); michael@0: message.dispose(); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void disableBatteryNotifications() { michael@0: GeckoBatteryManager.disableNotifications(); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetCurrentBatteryInformationWrapper") michael@0: public static double[] getCurrentBatteryInformation() { michael@0: return GeckoBatteryManager.getCurrentInformation(); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "CheckURIVisited") michael@0: static void checkUriVisited(String uri) { michael@0: GlobalHistory.getInstance().checkUriVisited(uri); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "MarkURIVisited") michael@0: static void markUriVisited(final String uri) { michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: GlobalHistory.getInstance().add(uri); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "SetURITitle") michael@0: static void setUriTitle(final String uri, final String title) { michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: GlobalHistory.getInstance().update(uri, title); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: static void hideProgressDialog() { michael@0: // unused stub michael@0: } michael@0: michael@0: /* michael@0: * WebSMS related methods. michael@0: */ michael@0: @WrapElementForJNI(stubName = "SendMessageWrapper") michael@0: public static void sendMessage(String aNumber, String aMessage, int aRequestId) { michael@0: if (SmsManager.getInstance() == null) { michael@0: return; michael@0: } michael@0: michael@0: SmsManager.getInstance().send(aNumber, aMessage, aRequestId); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetMessageWrapper") michael@0: public static void getMessage(int aMessageId, int aRequestId) { michael@0: if (SmsManager.getInstance() == null) { michael@0: return; michael@0: } michael@0: michael@0: SmsManager.getInstance().getMessage(aMessageId, aRequestId); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "DeleteMessageWrapper") michael@0: public static void deleteMessage(int aMessageId, int aRequestId) { michael@0: if (SmsManager.getInstance() == null) { michael@0: return; michael@0: } michael@0: michael@0: SmsManager.getInstance().deleteMessage(aMessageId, aRequestId); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "CreateMessageListWrapper") michael@0: public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) { michael@0: if (SmsManager.getInstance() == null) { michael@0: return; michael@0: } michael@0: michael@0: SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetNextMessageInListWrapper") michael@0: public static void getNextMessageInList(int aListId, int aRequestId) { michael@0: if (SmsManager.getInstance() == null) { michael@0: return; michael@0: } michael@0: michael@0: SmsManager.getInstance().getNextMessageInList(aListId, aRequestId); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void clearMessageList(int aListId) { michael@0: if (SmsManager.getInstance() == null) { michael@0: return; michael@0: } michael@0: michael@0: SmsManager.getInstance().clearMessageList(aListId); michael@0: } michael@0: michael@0: /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */ michael@0: @WrapElementForJNI michael@0: @RobocopTarget michael@0: public static boolean isTablet() { michael@0: return HardwareUtils.isTablet(); michael@0: } michael@0: michael@0: public static void viewSizeChanged() { michael@0: LayerView v = getLayerView(); michael@0: if (v != null && v.isIMEEnabled()) { michael@0: sendEventToGecko(GeckoEvent.createBroadcastEvent( michael@0: "ScrollTo:FocusedInput", "")); michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetCurrentNetworkInformationWrapper") michael@0: public static double[] getCurrentNetworkInformation() { michael@0: return GeckoNetworkManager.getInstance().getCurrentInformation(); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void enableNetworkNotifications() { michael@0: GeckoNetworkManager.getInstance().enableNotifications(); michael@0: } michael@0: michael@0: @WrapElementForJNI michael@0: public static void disableNetworkNotifications() { michael@0: GeckoNetworkManager.getInstance().disableNotifications(); michael@0: } michael@0: michael@0: // values taken from android's Base64 michael@0: public static final int BASE64_DEFAULT = 0; michael@0: public static final int BASE64_URL_SAFE = 8; michael@0: michael@0: /** michael@0: * taken from http://www.source-code.biz/base64coder/java/Base64Coder.java.txt and modified (MIT License) michael@0: */ michael@0: // Mapping table from 6-bit nibbles to Base64 characters. michael@0: private static final byte[] map1 = new byte[64]; michael@0: private static final byte[] map1_urlsafe; michael@0: static { michael@0: int i=0; michael@0: for (byte c='A'; c<='Z'; c++) map1[i++] = c; michael@0: for (byte c='a'; c<='z'; c++) map1[i++] = c; michael@0: for (byte c='0'; c<='9'; c++) map1[i++] = c; michael@0: map1[i++] = '+'; map1[i++] = '/'; michael@0: map1_urlsafe = map1.clone(); michael@0: map1_urlsafe[62] = '-'; map1_urlsafe[63] = '_'; michael@0: } michael@0: michael@0: // Mapping table from Base64 characters to 6-bit nibbles. michael@0: private static final byte[] map2 = new byte[128]; michael@0: static { michael@0: for (int i=0; i 127 || i1 > 127 || i2 > 127 || i3 > 127) michael@0: throw new IllegalArgumentException ("Illegal character in Base64 encoded data."); michael@0: int b0 = map2[i0]; michael@0: int b1 = map2[i1]; michael@0: int b2 = map2[i2]; michael@0: int b3 = map2[i3]; michael@0: if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) michael@0: throw new IllegalArgumentException ("Illegal character in Base64 encoded data."); michael@0: int o0 = ( b0 <<2) | (b1>>>4); michael@0: int o1 = ((b1 & 0xf)<<4) | (b2>>>2); michael@0: int o2 = ((b2 & 3)<<6) | b3; michael@0: out[op++] = (byte)o0; michael@0: if (op 5) { michael@0: type = src.substring(5, dataStart).replace(";base64", ""); michael@0: extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type); michael@0: } michael@0: michael@0: final File imageFile = File.createTempFile("image", "." + extension, dir); michael@0: os = new FileOutputStream(imageFile); michael@0: michael@0: byte[] buf = Base64.decode(src.substring(dataStart + 1), Base64.DEFAULT); michael@0: os.write(buf); michael@0: michael@0: // Only alter the intent when we're sure everything has worked michael@0: intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile)); michael@0: } else { michael@0: InputStream is = null; michael@0: try { michael@0: final byte[] buf = new byte[2048]; michael@0: final URL url = new URL(src); michael@0: final String filename = URLUtil.guessFileName(src, null, type); michael@0: is = url.openStream(); michael@0: michael@0: final File imageFile = new File(dir, filename); michael@0: os = new FileOutputStream(imageFile); michael@0: michael@0: int length; michael@0: while ((length = is.read(buf)) != -1) { michael@0: os.write(buf, 0, length); michael@0: } michael@0: michael@0: // Only alter the intent when we're sure everything has worked michael@0: intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile)); michael@0: } finally { michael@0: safeStreamClose(is); michael@0: } michael@0: } michael@0: } catch(IOException ex) { michael@0: // If something went wrong, we'll just leave the intent un-changed michael@0: } finally { michael@0: safeStreamClose(os); michael@0: } michael@0: } michael@0: michael@0: // Don't fail silently, tell the user that we weren't able to share the image michael@0: private static final void showImageShareFailureToast() { michael@0: Toast toast = Toast.makeText(getContext(), michael@0: getContext().getResources().getString(R.string.share_image_failed), michael@0: Toast.LENGTH_SHORT); michael@0: toast.show(); michael@0: } michael@0: michael@0: }