Bug 794981 - Part 1: Annotate Java methods that are to have JNI wrappers generated. r=kats
authorChris Kitching <ckitching@mozilla.com>
Mon, 09 Sep 2013 08:57:36 -0400
changeset 146179 f8a559f4f741b933e1d85ce96be15283e9b574f8
parent 146178 be8536d49a60e1449437b024837f10eb5b7f0742
child 146180 83220e4b96ec0f181d23a414fab5706b07afc2b9
push id25244
push userryanvm@gmail.com
push dateMon, 09 Sep 2013 20:03:14 +0000
treeherdermozilla-central@f320b8c034bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs794981
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 794981 - Part 1: Annotate Java methods that are to have JNI wrappers generated. r=kats
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoJavaSampler.java
mobile/android/base/Makefile.in
mobile/android/base/ThumbnailHelper.java
mobile/android/base/gfx/GLController.java
mobile/android/base/gfx/LayerView.java
mobile/android/base/gfx/NativePanZoomController.java
mobile/android/base/mozglue/GeneratableAndroidBridgeTarget.java
mobile/android/base/mozglue/OptionalGeneratedParameter.java
mobile/android/base/util/Clipboard.java
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -6,16 +6,18 @@
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.GfxInfoThread;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.mozglue.GeckoLoader;
+import org.mozilla.gecko.mozglue.GeneratableAndroidBridgeTarget;
+import org.mozilla.gecko.mozglue.OptionalGeneratedParameter;
 import org.mozilla.gecko.util.EventDispatcher;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.PendingIntent;
@@ -366,30 +368,33 @@ public class GeckoAppShell
     }
 
     // Tell the Gecko event loop that an event is available.
     public static native void notifyGeckoOfEvent(GeckoEvent event);
 
     /*
      *  The Gecko-side API: API methods that Gecko calls
      */
+    @GeneratableAndroidBridgeTarget(generateStatic = true)
     public static void notifyIME(int type) {
         if (mEditableListener != null) {
             mEditableListener.notifyIME(type);
         }
     }
 
+    @GeneratableAndroidBridgeTarget(generateStatic = true)
     public static void notifyIMEContext(int state, String typeHint,
                                         String modeHint, String actionHint) {
         if (mEditableListener != null) {
             mEditableListener.notifyIMEContext(state, typeHint,
                                                modeHint, actionHint);
         }
     }
 
+    @GeneratableAndroidBridgeTarget(generateStatic = true)
     public static void notifyIMEChange(String text, int start, int end, int newEnd) {
         if (newEnd < 0) { // Selection change
             mEditableListener.onSelectionChange(start, end);
         } else { // Text change
             mEditableListener.onTextChange(text, start, end, newEnd);
         }
     }
 
@@ -423,16 +428,17 @@ public class GeckoAppShell
                 }
                 long waited = SystemClock.uptimeMillis() - time;
                 Log.d(LOGTAG, "Gecko event sync taking too long: " + waited + "ms");
             }
         }
     }
 
     // Signal the Java thread that it's time to wake up
+    @GeneratableAndroidBridgeTarget
     public static void acknowledgeEvent() {
         synchronized (sEventAckLock) {
             sWaitingForEventAck = false;
             sEventAckLock.notifyAll();
         }
     }
 
     private static float getLocationAccuracy(Location location) {
@@ -461,16 +467,17 @@ public class GeckoAppShell
                  getLocationAccuracy(location) < getLocationAccuracy(lastKnownLocation))) {
                 lastKnownLocation = location;
             }
         }
 
         return lastKnownLocation;
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void enableLocation(final boolean enable) {
         ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     LocationManager lm = getLocationManager(getContext());
                     if (lm == null) {
                         return;
                     }
@@ -516,20 +523,22 @@ public class GeckoAppShell
             // which allows enabling/disabling location update notifications from the cell radio.
             // CONTROL_LOCATION_UPDATES is not for use by normal applications, but we might be
             // hitting this problem if the Tegras are confused about missing cell radios.
             Log.e(LOGTAG, "LOCATION_SERVICE not found?!", e);
             return null;
         }
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void enableLocationHighAccuracy(final boolean enable) {
         mLocationHighAccuracy = enable;
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void enableSensor(int aSensortype) {
         GeckoInterface gi = getGeckoInterface();
         if (gi == null)
             return;
         SensorManager sm = (SensorManager)
             getContext().getSystemService(Context.SENSOR_SERVICE);
 
         switch(aSensortype) {
@@ -574,16 +583,17 @@ public class GeckoAppShell
             if (gGyroscopeSensor != null)
                 sm.registerListener(gi.getSensorEventListener(), gGyroscopeSensor, sDefaultSensorHint);
             break;
         default:
             Log.w(LOGTAG, "Error! Can't enable unknown SENSOR type " + aSensortype);
         }
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void disableSensor(int aSensortype) {
         GeckoInterface gi = getGeckoInterface();
         if (gi == null)
             return;
 
         SensorManager sm = (SensorManager)
             getContext().getSystemService(Context.SENSOR_SERVICE);
 
@@ -617,41 +627,44 @@ public class GeckoAppShell
             if (gGyroscopeSensor != null)
                 sm.unregisterListener(gi.getSensorEventListener(), gGyroscopeSensor);
             break;
         default:
             Log.w(LOGTAG, "Error! Can't disable unknown SENSOR type " + aSensortype);
         }
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void moveTaskToBack() {
         if (getGeckoInterface() != null)
             getGeckoInterface().getActivity().moveTaskToBack(true);
     }
 
     public static void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
         // This method may be called from JNI to report Gecko's current selection indexes, but
         // Native Fennec doesn't care because the Java code already knows the selection indexes.
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "NotifyXreExit")
     static void onXreExit() {
         // The launch state can only be Launched or GeckoRunning at this point
         GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoExiting);
         if (getGeckoInterface() != null) {
             if (gRestartScheduled) {
                 getGeckoInterface().doRestart();
             } else {
                 getGeckoInterface().getActivity().finish();
             }
         }
 
         Log.d(LOGTAG, "Killing via System.exit()");
         System.exit(0);
     }
 
+    @GeneratableAndroidBridgeTarget
     static void scheduleRestart() {
         gRestartScheduled = true;
     }
 
     public static File preInstallWebApp(String aTitle, String aURI, String aOrigin) {
         int index = WebAppAllocator.getInstance(getContext()).findAndAllocateIndex(aOrigin, aTitle, (String) null);
         GeckoProfile profile = GeckoProfile.get(getContext(), "webapp" + index);
         return profile.getDir();
@@ -684,16 +697,17 @@ public class GeckoAppShell
         intent.setData(Uri.parse(aURI));
         intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
                             AppConstants.ANDROID_PACKAGE_NAME + ".WebApps$WebApp" + aIndex);
         return intent;
     }
 
     // "Installs" an application by creating a shortcut
     // This is the entry point from AndroidBridge.h
+    @GeneratableAndroidBridgeTarget
     static void createShortcut(String aTitle, String aURI, String aIconData, String aType) {
         if ("webapp".equals(aType)) {
             Log.w(LOGTAG, "createShortcut with no unique URI should not be used for aType = webapp!");
         }
 
         createShortcut(aTitle, aURI, aURI, aIconData, aType);
     }
 
@@ -878,23 +892,25 @@ public class GeckoAppShell
                                    halfSize - sHeight,
                                    halfSize + sWidth,
                                    halfSize + sHeight),
                           null);
 
         return bitmap;
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetHandlersForMimeTypeWrapper")
     static String[] getHandlersForMimeType(String aMimeType, String aAction) {
         Intent intent = getIntentForActionString(aAction);
         if (aMimeType != null && aMimeType.length() > 0)
             intent.setType(aMimeType);
         return getHandlersForIntent(intent);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetHandlersForURLWrapper")
     static String[] getHandlersForURL(String aURL, String aAction) {
         // aURL may contain the whole URL or just the protocol
         Uri uri = aURL.indexOf(':') >= 0 ? Uri.parse(aURL) : new Uri.Builder().scheme(aURL).build();
 
         Intent intent = getOpenURIIntent(getContext(), uri.toString(), "",
             TextUtils.isEmpty(aAction) ? Intent.ACTION_VIEW : aAction, "");
 
         return getHandlersForIntent(intent);
@@ -927,20 +943,22 @@ public class GeckoAppShell
     static Intent getIntentForActionString(String aAction) {
         // Default to the view action if no other action as been specified.
         if (TextUtils.isEmpty(aAction)) {
             return new Intent(Intent.ACTION_VIEW);
         }
         return new Intent(aAction);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetExtensionFromMimeTypeWrapper")
     static String getExtensionFromMimeType(String aMimeType) {
         return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetMimeTypeFromExtensionsWrapper")
     static String getMimeTypeFromExtensions(String aFileExt) {
         MimeTypeMap mtm = MimeTypeMap.getSingleton();
         StringTokenizer st = new StringTokenizer(aFileExt, ".,; ");
         String type = null;
         String subType = null;
         while (st.hasMoreElements()) {
             String ext = st.nextToken();
             String mt = mtm.getMimeTypeFromExtension(ext);
@@ -1064,22 +1082,23 @@ public class GeckoAppShell
      *
      * @param targetURI the string spec of the URI to open.
      * @param mimeType an optional MIME type string.
      * @param action an Android action specifier, such as
      *               <code>Intent.ACTION_SEND</code>.
      * @param title the title to use in <code>ACTION_SEND</code> intents.
      * @return true if the activity started successfully; false otherwise.
      */
+    @GeneratableAndroidBridgeTarget
     public static boolean openUriExternal(String targetURI,
                                           String mimeType,
-                                          String packageName,
-                                          String className,
-                                          String action,
-                                          String title) {
+              @OptionalGeneratedParameter String packageName,
+              @OptionalGeneratedParameter String className,
+              @OptionalGeneratedParameter String action,
+              @OptionalGeneratedParameter String title) {
         final Context context = getContext();
         final Intent intent = getOpenURIIntent(context, targetURI,
                                                mimeType, action, title);
 
         if (intent == null) {
             return false;
         }
 
@@ -1249,16 +1268,17 @@ public class GeckoAppShell
     public static void setNotificationClient(NotificationClient client) {
         if (sNotificationClient == null) {
             sNotificationClient = client;
         } else {
             Log.d(LOGTAG, "Notification client already set");
         }
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "ShowAlertNotificationWrapper")
     public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText,
                                              String aAlertCookie, String aAlertName) {
         // The intent to launch when the user clicks the expanded notification
         String app = getContext().getClass().getName();
         Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CALLBACK);
         notificationIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, app);
         notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
@@ -1275,21 +1295,23 @@ public class GeckoAppShell
                 getContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
 
         mAlertCookies.put(aAlertName, aAlertCookie);
         callObserver(aAlertName, "alertshow", aAlertCookie);
 
         sNotificationClient.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent);
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) {
         int notificationID = aAlertName.hashCode();
         sNotificationClient.update(notificationID, aProgress, aProgressMax, aAlertText);
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void closeNotification(String aAlertName) {
         String alertCookie = mAlertCookies.get(aAlertName);
         if (alertCookie != null) {
             callObserver(aAlertName, "alertfinished", alertCookie);
             mAlertCookies.remove(aAlertName);
         }
 
         removeObserver(aAlertName);
@@ -1313,16 +1335,17 @@ public class GeckoAppShell
         // Also send a notification to the observer service
         // New listeners should register for these notifications since they will be called even if
         // Gecko has been killed and restared between when your notification was shown and when the
         // user clicked on it.
         sendEventToGecko(GeckoEvent.createBroadcastEvent("Notification:Clicked", aAlertCookie));
         closeNotification(aAlertName);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetDpiWrapper")
     public static int getDpi() {
         if (sDensityDpi == 0) {
             sDensityDpi = getContext().getResources().getDisplayMetrics().densityDpi;
         }
 
         return sDensityDpi;
     }
 
@@ -1351,16 +1374,17 @@ public class GeckoAppShell
         }
         return false;
     }
 
     /**
      * Returns the colour depth of the default screen. This will either be
      * 24 or 16.
      */
+    @GeneratableAndroidBridgeTarget(stubName = "GetScreenDepthWrapper")
     public static synchronized int getScreenDepth() {
         if (sScreenDepth == 0) {
             sScreenDepth = 16;
             PixelFormat info = new PixelFormat();
             PixelFormat.getPixelFormatInfo(getGeckoInterface().getActivity().getWindowManager().getDefaultDisplay().getPixelFormat(), info);
             if (info.bitsPerPixel >= 24 && isHighMemoryDevice()) {
                 sScreenDepth = 24;
             }
@@ -1373,120 +1397,133 @@ public class GeckoAppShell
         if (sScreenDepth != 0) {
             Log.e(LOGTAG, "Tried to override screen depth after it's already been set");
             return;
         }
 
         sScreenDepth = aScreenDepth;
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void setFullScreen(boolean fullscreen) {
         if (getGeckoInterface() != null)
             getGeckoInterface().setFullScreen(fullscreen);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "ShowFilePickerForExtensionsWrapper")
     public static String showFilePickerForExtensions(String aExtensions) {
         if (getGeckoInterface() != null)
             return sActivityHelper.showFilePicker(getGeckoInterface().getActivity(), getMimeTypeFromExtensions(aExtensions));
         return "";
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "ShowFilePickerForMimeTypeWrapper")
     public static String showFilePickerForMimeType(String aMimeType) {
         if (getGeckoInterface() != null)
             return sActivityHelper.showFilePicker(getGeckoInterface().getActivity(), aMimeType);
         return "";
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void performHapticFeedback(boolean aIsLongPress) {
         // Don't perform haptic feedback if a vibration is currently playing,
         // because the haptic feedback will nuke the vibration.
         if (!sVibrationMaybePlaying || System.nanoTime() >= sVibrationEndTime) {
             LayerView layerView = getLayerView();
             layerView.performHapticFeedback(aIsLongPress ?
                                             HapticFeedbackConstants.LONG_PRESS :
                                             HapticFeedbackConstants.VIRTUAL_KEY);
         }
     }
 
     private static Vibrator vibrator() {
         LayerView layerView = getLayerView();
         return (Vibrator) layerView.getContext().getSystemService(Context.VIBRATOR_SERVICE);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "Vibrate1")
     public static void vibrate(long milliseconds) {
         sVibrationEndTime = System.nanoTime() + milliseconds * 1000000;
         sVibrationMaybePlaying = true;
         vibrator().vibrate(milliseconds);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "VibrateA")
     public static void vibrate(long[] pattern, int repeat) {
         // If pattern.length is even, the last element in the pattern is a
         // meaningless delay, so don't include it in vibrationDuration.
         long vibrationDuration = 0;
         int iterLen = pattern.length - (pattern.length % 2 == 0 ? 1 : 0);
         for (int i = 0; i < iterLen; i++) {
           vibrationDuration += pattern[i];
         }
 
         sVibrationEndTime = System.nanoTime() + vibrationDuration * 1000000;
         sVibrationMaybePlaying = true;
         vibrator().vibrate(pattern, repeat);
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void cancelVibrate() {
         sVibrationMaybePlaying = false;
         sVibrationEndTime = 0;
         vibrator().cancel();
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void showInputMethodPicker() {
         InputMethodManager imm = (InputMethodManager)
             getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
         imm.showInputMethodPicker();
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void setKeepScreenOn(final boolean on) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 // TODO
             }
         });
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void notifyDefaultPrevented(final boolean defaultPrevented) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 LayerView view = getLayerView();
                 PanZoomController controller = (view == null ? null : view.getPanZoomController());
                 if (controller != null) {
                     controller.notifyDefaultActionPrevented(defaultPrevented);
                 }
             }
         });
     }
 
+    @GeneratableAndroidBridgeTarget
     public static boolean isNetworkLinkUp() {
         ConnectivityManager cm = (ConnectivityManager)
            getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
         NetworkInfo info = cm.getActiveNetworkInfo();
         if (info == null || !info.isConnected())
             return false;
         return true;
     }
 
+    @GeneratableAndroidBridgeTarget
     public static boolean isNetworkLinkKnown() {
         ConnectivityManager cm = (ConnectivityManager)
             getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
         if (cm.getActiveNetworkInfo() == null)
             return false;
         return true;
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void setSelectedLocale(String localeCode) {
         /* Bug 713464: This method is still called from Gecko side.
            Earlier we had an option to run Firefox in a language other than system's language.
            However, this is not supported as of now.
            Gecko resets the locale to en-US by calling this function with an empty string.
            This affects GeckoPreferences activity in multi-locale builds.
 
         //We're not using this, not need to save it (see bug 635342)
@@ -1508,16 +1545,18 @@ public class GeckoAppShell
 
         Resources res = getContext().getBaseContext().getResources();
         Configuration config = res.getConfiguration();
         config.locale = locale;
         res.updateConfiguration(config, res.getDisplayMetrics());
         */
     }
 
+
+    @GeneratableAndroidBridgeTarget(stubName = "GetSystemColoursWrapper")
     public static int[] getSystemColors() {
         // attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h
         final int[] attrsAppearance = {
             android.R.attr.textColor,
             android.R.attr.textColorPrimary,
             android.R.attr.textColorPrimaryInverse,
             android.R.attr.textColorSecondary,
             android.R.attr.textColorSecondaryInverse,
@@ -1544,16 +1583,17 @@ public class GeckoAppShell
                 result[idx] = color;
             }
             appearance.recycle();
         }
 
         return result;
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void killAnyZombies() {
         GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() {
             @Override
             public boolean callback(int pid) {
                 if (pid != android.os.Process.myPid())
                     android.os.Process.killProcess(pid);
                 return true;
             }
@@ -1699,21 +1739,23 @@ public class GeckoAppShell
                 String file = split[nameColumn];
                 if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(file) && file.startsWith(filter))
                     Log.i(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file);
             }
             in.close();
         } catch (Exception e) { }
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void scanMedia(String aFile, String aMimeType) {
         Context context = getContext();
         GeckoMediaScannerClient.startScan(context, aFile, aMimeType);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetIconForExtensionWrapper")
     public static byte[] getIconForExtension(String aExt, int iconSize) {
         try {
             if (iconSize <= 0)
                 iconSize = 16;
 
             if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.')
                 aExt = aExt.substring(1);
 
@@ -1757,36 +1799,39 @@ public class GeckoAppShell
         if (resolveInfo == null)
             return null;
 
         ActivityInfo activityInfo = resolveInfo.activityInfo;
 
         return activityInfo.loadIcon(pm);
     }
 
+    @GeneratableAndroidBridgeTarget
     public static boolean getShowPasswordSetting() {
         try {
             int showPassword =
                 Settings.System.getInt(getContext().getContentResolver(),
                                        Settings.System.TEXT_SHOW_PASSWORD, 1);
             return (showPassword > 0);
         }
         catch (Exception e) {
             return true;
         }
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "AddPluginViewWrapper")
     public static void addPluginView(View view,
                                      float x, float y,
                                      float w, float h,
                                      boolean isFullScreen) {
         if (getGeckoInterface() != null)
              getGeckoInterface().addPluginView(view, new RectF(x, y, x + w, y + h), isFullScreen);
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void removePluginView(View view, boolean isFullScreen) {
         if (getGeckoInterface() != null)
             getGeckoInterface().removePluginView(view, isFullScreen);
     }
 
     /**
      * A plugin that wish to be loaded in the WebView must provide this permission
      * in their AndroidManifest.xml.
@@ -2000,16 +2045,17 @@ public class GeckoAppShell
         } catch (android.content.pm.PackageManager.NameNotFoundException nnfe) {
             Log.w(LOGTAG, "Couldn't find package.", nnfe);
             return null;
         }
     }
 
     private static ContextGetter sContextGetter;
 
+    @GeneratableAndroidBridgeTarget(allowMultithread = true)
     public static Context getContext() {
         return sContextGetter.getContext();
     }
 
     public static void setContextGetter(ContextGetter cg) {
         sContextGetter = cg;
     }
 
@@ -2056,16 +2102,18 @@ public class GeckoAppShell
 
     public static android.hardware.Camera sCamera = null;
 
     static native void cameraCallbackBridge(byte[] data);
 
     static int kPreferedFps = 25;
     static byte[] sCameraBuffer = null;
 
+
+    @GeneratableAndroidBridgeTarget(stubName = "InitCameraWrapper")
     static int[] initCamera(String aContentType, int aCamera, int aWidth, int aHeight) {
         ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     try {
                         if (getGeckoInterface() != null)
                             getGeckoInterface().enableCameraView();
                     } catch (Exception e) {}
@@ -2156,16 +2204,17 @@ public class GeckoAppShell
             result[3] = params.getPreviewFrameRate();
         } catch(RuntimeException e) {
             Log.w(LOGTAG, "initCamera RuntimeException.", e);
             result[0] = result[1] = result[2] = result[3] = 0;
         }
         return result;
     }
 
+    @GeneratableAndroidBridgeTarget
     static synchronized void closeCamera() {
         ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     try {
                         if (getGeckoInterface() != null)
                             getGeckoInterface().disableCameraView();
                     } catch (Exception e) {}
@@ -2207,130 +2256,148 @@ public class GeckoAppShell
      */
     public static void unregisterEventListener(String event, GeckoEventListener listener) {
         sEventDispatcher.unregisterEventListener(event, listener);
     }
 
     /*
      * Battery API related methods.
      */
+    @GeneratableAndroidBridgeTarget
     public static void enableBatteryNotifications() {
         GeckoBatteryManager.enableNotifications();
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "HandleGeckoMessageWrapper")
     public static String handleGeckoMessage(String message) {
         return sEventDispatcher.dispatchEvent(message);
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void disableBatteryNotifications() {
         GeckoBatteryManager.disableNotifications();
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetCurrentBatteryInformationWrapper")
     public static double[] getCurrentBatteryInformation() {
         return GeckoBatteryManager.getCurrentInformation();
     }
 
-    static void checkUriVisited(String uri) {   // invoked from native JNI code
+    @GeneratableAndroidBridgeTarget(stubName = "CheckURIVisited")
+    static void checkUriVisited(String uri) {
         GlobalHistory.getInstance().checkUriVisited(uri);
     }
 
-    static void markUriVisited(final String uri) {    // invoked from native JNI code
+    @GeneratableAndroidBridgeTarget(stubName = "MarkURIVisited")
+    static void markUriVisited(final String uri) {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 GlobalHistory.getInstance().add(uri);
             }
         });
     }
 
-    static void setUriTitle(final String uri, final String title) {    // invoked from native JNI code
+    @GeneratableAndroidBridgeTarget(stubName = "SetURITitle")
+    static void setUriTitle(final String uri, final String title) {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 GlobalHistory.getInstance().update(uri, title);
             }
         });
     }
 
+    @GeneratableAndroidBridgeTarget
     static void hideProgressDialog() {
         // unused stub
     }
 
     /*
      * WebSMS related methods.
      */
+    @GeneratableAndroidBridgeTarget(stubName = "SendMessageWrapper")
     public static void sendMessage(String aNumber, String aMessage, int aRequestId) {
         if (SmsManager.getInstance() == null) {
             return;
         }
 
         SmsManager.getInstance().send(aNumber, aMessage, aRequestId);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetMessageWrapper")
     public static void getMessage(int aMessageId, int aRequestId) {
         if (SmsManager.getInstance() == null) {
             return;
         }
 
         SmsManager.getInstance().getMessage(aMessageId, aRequestId);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "DeleteMessageWrapper")
     public static void deleteMessage(int aMessageId, int aRequestId) {
         if (SmsManager.getInstance() == null) {
             return;
         }
 
         SmsManager.getInstance().deleteMessage(aMessageId, aRequestId);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "CreateMessageListWrapper")
     public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
         if (SmsManager.getInstance() == null) {
             return;
         }
 
         SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetNextMessageInListWrapper")
     public static void getNextMessageInList(int aListId, int aRequestId) {
         if (SmsManager.getInstance() == null) {
             return;
         }
 
         SmsManager.getInstance().getNextMessageInList(aListId, aRequestId);
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void clearMessageList(int aListId) {
         if (SmsManager.getInstance() == null) {
             return;
         }
 
         SmsManager.getInstance().clearMessageList(aListId);
     }
 
     /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */
+    @GeneratableAndroidBridgeTarget
     public static boolean isTablet() {
         return HardwareUtils.isTablet();
     }
 
     public static void viewSizeChanged() {
         LayerView v = getLayerView();
         if (v != null && v.isIMEEnabled()) {
             sendEventToGecko(GeckoEvent.createBroadcastEvent(
                     "ScrollTo:FocusedInput", ""));
         }
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetCurrentNetworkInformationWrapper")
     public static double[] getCurrentNetworkInformation() {
         return GeckoNetworkManager.getInstance().getCurrentInformation();
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void enableNetworkNotifications() {
         GeckoNetworkManager.getInstance().enableNotifications();
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void disableNetworkNotifications() {
         GeckoNetworkManager.getInstance().disableNotifications();
     }
 
     // values taken from android's Base64
     public static final int BASE64_DEFAULT = 0;
     public static final int BASE64_URL_SAFE = 8;
 
@@ -2434,36 +2501,42 @@ public class GeckoAppShell
             if (op<oLen) out[op++] = (byte)o2; }
         return out; 
     }
 
     public static byte[] decodeBase64(String s, int flags) {
         return decodeBase64(s.getBytes(), flags);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetScreenOrientationWrapper")
     public static short getScreenOrientation() {
         return GeckoScreenOrientationListener.getInstance().getScreenOrientation();
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void enableScreenOrientationNotifications() {
         GeckoScreenOrientationListener.getInstance().enableNotifications();
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void disableScreenOrientationNotifications() {
         GeckoScreenOrientationListener.getInstance().disableNotifications();
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void lockScreenOrientation(int aOrientation) {
         GeckoScreenOrientationListener.getInstance().lockScreenOrientation(aOrientation);
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void unlockScreenOrientation() {
         GeckoScreenOrientationListener.getInstance().unlockScreenOrientation();
     }
 
+    @GeneratableAndroidBridgeTarget
     public static boolean pumpMessageLoop() {
         Handler geckoHandler = ThreadUtils.sGeckoHandler;
         Message msg = getNextMessageFromQueue(ThreadUtils.sGeckoQueue);
 
         if (msg == null)
             return false;
         if (msg.obj == geckoHandler && msg.getTarget() == geckoHandler) {
             // Our "queue is empty" message; see runGecko()
@@ -2475,59 +2548,66 @@ public class GeckoAppShell
         else
             msg.getTarget().dispatchMessage(msg);
         msg.recycle();
         return true;
     }
 
     static native void notifyFilePickerResult(String filePath, long id);
 
+    @GeneratableAndroidBridgeTarget(stubName = "ShowFilePickerAsyncWrapper")
     public static void showFilePickerAsync(String aMimeType, final long id) {
         sActivityHelper.showFilePickerAsync(getGeckoInterface().getActivity(), aMimeType, new ActivityHandlerHelper.FileResultHandler() {
             public void gotFile(String filename) {
                 GeckoAppShell.notifyFilePickerResult(filename, id);
             }
         });
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void notifyWakeLockChanged(String topic, String state) {
         if (getGeckoInterface() != null)
             getGeckoInterface().notifyWakeLockChanged(topic, state);
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetGfxInfoDataWrapper")
     public static String getGfxInfoData() {
         return GfxInfoThread.getData();
     }
 
+    @GeneratableAndroidBridgeTarget
     public static void registerSurfaceTextureFrameListener(Object surfaceTexture, final int id) {
         ((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
             @Override
             public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                 GeckoAppShell.onSurfaceTextureFrameAvailable(surfaceTexture, id);
             }
         });
     }
 
+    @GeneratableAndroidBridgeTarget(allowMultithread = true)
     public static void unregisterSurfaceTextureFrameListener(Object surfaceTexture) {
         ((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(null);
     }
 
+    @GeneratableAndroidBridgeTarget
     public static boolean unlockProfile() {
         // Try to kill any zombie Fennec's that might be running
         GeckoAppShell.killAnyZombies();
 
         // Then force unlock this profile
         if (getGeckoInterface() != null) {
             GeckoProfile profile = getGeckoInterface().getProfile();
             File lock = profile.getFile(".parentlock");
             return lock.exists() && lock.delete();
         }
         return false;
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetProxyForURIWrapper")
     public static String getProxyForURI(String spec, String scheme, String host, int port) {
         URI uri = null;
         try {
             uri = new URI(spec);
         } catch(java.net.URISyntaxException uriEx) {
             try {
                 uri = new URI(scheme, null, host, port, null, null, null);
             } catch(java.net.URISyntaxException uriEx2) {
--- a/mobile/android/base/GeckoJavaSampler.java
+++ b/mobile/android/base/GeckoJavaSampler.java
@@ -2,16 +2,19 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import android.os.SystemClock;
 import android.util.Log;
+
+import org.mozilla.gecko.mozglue.GeneratableAndroidBridgeTarget;
+
 import java.lang.Thread;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
 public class GeckoJavaSampler {
     private static final String LOGTAG = "JavaSampler";
     private static Thread sSamplingThread = null;
@@ -117,74 +120,83 @@ public class GeckoJavaSampler {
                 }
                 int readPos = (startPos + aSampleId) % mSamples.get(aThreadId).length;
                 return mSamples.get(aThreadId)[readPos];
             }
             return null;
         }
     }
 
+
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "GetThreadNameJavaProfilingWrapper")
     public synchronized static String getThreadName(int aThreadId) {
         if (aThreadId == 0 && sMainThread != null) {
             return sMainThread.getName();
         }
         return null;
     }
 
     private synchronized static Sample getSample(int aThreadId, int aSampleId) {
         return sSamplingRunnable.getSample(aThreadId, aSampleId);
     }
 
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "GetSampleTimeJavaProfiling")
     public synchronized static double getSampleTime(int aThreadId, int aSampleId) {
         Sample sample = getSample(aThreadId, aSampleId);
         if (sample != null) {
             if (sample.mJavaTime != 0) {
                 return (double)(sample.mJavaTime -
                     SystemClock.elapsedRealtime()) + getProfilerTime();
             }
             System.out.println("Sample: " + sample.mTime);
             return sample.mTime;
         }
         return 0;
     }
+
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "GetFrameNameJavaProfilingWrapper")
     public synchronized static String getFrameName(int aThreadId, int aSampleId, int aFrameId) {
         Sample sample = getSample(aThreadId, aSampleId);
         if (sample != null && aFrameId < sample.mFrames.length) {
             Frame frame = sample.mFrames[aFrameId];
             if (frame == null) {
                 return null;
             }
             return frame.className + "." + frame.methodName + "()";
         }
         return null;
     }
 
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "StartJavaProfiling")
     public static void start(int aInterval, int aSamples) {
         synchronized (GeckoJavaSampler.class) {
             if (sSamplingRunnable != null) {
                 return;
             }
             sSamplingRunnable = new SamplingThread(aInterval, aSamples);
             sSamplingThread = new Thread(sSamplingRunnable, "Java Sampler");
             sSamplingThread.start();
         }
     }
 
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "PauseJavaProfiling")
     public static void pause() {
         synchronized (GeckoJavaSampler.class) {
             sSamplingRunnable.mPauseSampler = true;
         }
     }
 
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "UnpauseJavaProfiling")
     public static void unpause() {
         synchronized (GeckoJavaSampler.class) {
             sSamplingRunnable.mPauseSampler = false;
         }
     }
 
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "StopJavaProfiling")
     public static void stop() {
         synchronized (GeckoJavaSampler.class) {
             if (sSamplingThread == null) {
                 return;
             }
 
             sSamplingRunnable.mStopSampler = true;
             try {
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -4,18 +4,20 @@
 
 DIST_FILES = package-name.txt
 
 include $(topsrcdir)/mobile/android/base/android-services-files.mk
 
 MOZGLUE_JAVA_FILES := \
   mozglue/ByteBufferInputStream.java \
   mozglue/DirectBufferAllocator.java \
+  mozglue/GeneratableAndroidBridgeTarget.java \
   mozglue/NativeReference.java \
   mozglue/NativeZip.java \
+  mozglue/OptionalGeneratedParameter.java \
   $(NULL)
 
 MOZGLUE_PP_JAVA_FILES := \
   mozglue/GeckoLoader.java \
   $(NULL)
 
 UTIL_JAVA_FILES := \
   util/ActivityResultHandler.java \
@@ -1259,20 +1261,20 @@ jars/sync-thirdparty.jar: $(addprefix $(
 
 jars/websockets.jar: $(addprefix $(srcdir)/, $(WEBSOCKETS_JAVA_FILES)) jars
 	@echo "JAR websockets.jar"
 	$(NSINSTALL) -D classes/websockets
 	$(JAVAC) $(JAVAC_FLAGS) -Xlint:all,-serial -d classes/websockets $(addprefix $(srcdir)/,$(WEBSOCKETS_JAVA_FILES))
 	$(JAR) cMf jars/websockets.jar -C classes/websockets .
 
 ifdef MOZ_WEBRTC
-jars/webrtc.jar: $(addprefix $(srcdir)/, $(WEBRTC_JAVA_FILES)) jars jars/gecko-browser.jar jars/gecko-util.jar
+jars/webrtc.jar: $(addprefix $(srcdir)/, $(WEBRTC_JAVA_FILES)) jars jars/gecko-browser.jar jars/gecko-util.jar jars/gecko-mozglue.jar
 	@echo "JAR webrtc.jar"
 	$(NSINSTALL) -D classes/webrtc
-	$(JAVAC) $(JAVAC_FLAGS) -d classes/webrtc -classpath classes/gecko-browser:classes/gecko-util $(addprefix $(srcdir)/,$(WEBRTC_JAVA_FILES))
+	$(JAVAC) $(JAVAC_FLAGS) -d classes/webrtc -classpath classes/gecko-browser:classes/gecko-util:classes/gecko-mozglue $(addprefix $(srcdir)/,$(WEBRTC_JAVA_FILES))
 	$(JAR) cMf jars/webrtc.jar -C classes/webrtc .
 endif
 
 jars:
 	@echo "MKDIR jars"
 	$(NSINSTALL) -D jars
 
 CLASSES_WITH_JNI= \
--- a/mobile/android/base/ThumbnailHelper.java
+++ b/mobile/android/base/ThumbnailHelper.java
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.mozglue.DirectBufferAllocator;
+import org.mozilla.gecko.mozglue.GeneratableAndroidBridgeTarget;
 
 import android.graphics.Bitmap;
 import android.util.Log;
 
 import java.nio.ByteBuffer;
 import java.util.LinkedList;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -138,16 +139,17 @@ public final class ThumbnailHelper {
             return;
         }
 
         GeckoEvent e = GeckoEvent.createThumbnailEvent(tab.getId(), mWidth, mHeight, mBuffer);
         GeckoAppShell.sendEventToGecko(e);
     }
 
     /* This method is invoked by JNI once the thumbnail data is ready. */
+    @GeneratableAndroidBridgeTarget(stubName = "SendThumbnail")
     public static void notifyThumbnail(ByteBuffer data, int tabId, boolean success) {
         Tab tab = Tabs.getInstance().getTab(tabId);
         ThumbnailHelper helper = ThumbnailHelper.getInstance();
         if (success && tab != null) {
             helper.handleThumbnailData(tab, data);
         }
         helper.processNextThumbnail(tab);
     }
--- a/mobile/android/base/gfx/GLController.java
+++ b/mobile/android/base/gfx/GLController.java
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoThread;
+import org.mozilla.gecko.mozglue.GeneratableAndroidBridgeTarget;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.util.Log;
 
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.egl.EGLContext;
 import javax.microedition.khronos.egl.EGLDisplay;
@@ -257,17 +258,17 @@ public class GLController {
             if (red[0] == rSize && green[0] == gSize && blue[0] == bSize) {
                 return config;
             }
         }
 
         throw new GLControllerException("No suitable EGL configuration found");
     }
 
-    /* This function is invoked by JNI on the compositor thread */
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "ProvideEGLSurfaceWrapper")
     private EGLSurface provideEGLSurface() {
         return mEGLSurface;
     }
 
     private String getEGLError() {
         return "Error " + (mEGL == null ? "(no mEGL)" : mEGL.eglGetError());
     }
 
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.GeckoAccessibility;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.TouchEventInterceptor;
 import org.mozilla.gecko.ZoomConstraints;
+import org.mozilla.gecko.mozglue.GeneratableAndroidBridgeTarget;
 import org.mozilla.gecko.util.EventDispatcher;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.PointF;
@@ -502,17 +503,17 @@ public class LayerView extends FrameLayo
 
     public Object getNativeWindow() {
         if (mSurfaceView != null)
             return mSurfaceView.getHolder();
 
         return mTextureView.getSurfaceTexture();
     }
 
-    /** This function is invoked by Gecko (compositor thread) via JNI; be careful when modifying signature. */
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "RegisterCompositorWrapper")
     public static GLController registerCxxCompositor() {
         try {
             LayerView layerView = GeckoAppShell.getLayerView();
             GLController controller = layerView.getGLController();
             controller.compositorCreated();
             return controller;
         } catch (Exception e) {
             Log.e(LOGTAG, "Error registering compositor!", e);
--- a/mobile/android/base/gfx/NativePanZoomController.java
+++ b/mobile/android/base/gfx/NativePanZoomController.java
@@ -2,16 +2,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoThread;
+import org.mozilla.gecko.mozglue.GeneratableAndroidBridgeTarget;
 import org.mozilla.gecko.util.EventDispatcher;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONObject;
 
 import android.graphics.PointF;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -77,22 +78,22 @@ class NativePanZoomController implements
     private native long runDelayedCallback();
 
     public native void destroy();
     public native void notifyDefaultActionPrevented(boolean prevented);
     public native boolean getRedrawHint();
     public native void setOverScrollMode(int overscrollMode);
     public native int getOverScrollMode();
 
-    /* Invoked from JNI */
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "RequestContentRepaintWrapper")
     private void requestContentRepaint(float x, float y, float width, float height, float resolution) {
         mTarget.forceRedraw(new DisplayPortMetrics(x, y, x + width, y + height, resolution));
     }
 
-    /* Invoked from JNI */
+    @GeneratableAndroidBridgeTarget(allowMultithread = true, stubName = "PostDelayedCallbackWrapper")
     private void postDelayedCallback(long delay) {
         mTarget.postDelayed(mCallbackRunnable, delay);
     }
 
     class CallbackRunnable implements Runnable {
         @Override
         public void run() {
             long nextDelay = runDelayedCallback();
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/mozglue/GeneratableAndroidBridgeTarget.java
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.mozglue;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This annotation is used to tag methods that are to have wrapper methods generated in the android
+ * bridge. Such methods will be protected from destruction by Proguard, and allow us to avoid writing
+ * by hand large amounts of boring boilerplate.
+ * An annotated Java method will have a corresponding method generated in the android bridge.
+ * By default, the name of the generated method will be the same as the name of the Java method, with
+ * the first letter in upper case. The stubName property may be used to specify a custom name for the
+ * generated method stub.
+ *
+ * allowMultithreaded should be used as sparingly as possible - the resulting code will allow the
+ * Java method to be invoked from the C side from multiple threads. Often, this isn't what is wanted
+ * and may lead to subtle bugs.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface GeneratableAndroidBridgeTarget {
+    // Optional parameter specifying the name of the generated method stub. If omitted, the name
+    // of the Java method will be used.
+    String stubName() default "";
+
+    // Optional parameter specifying if the generated method should be a static member of AndroidBridge
+    // By default, an instance member is produced. This is almost always what is wanted.
+    boolean generateStatic() default false;
+
+    /**
+     * If set, the generated method stub will support being called from any thread via the use of
+     * GetJNIForThread. This is rarely useful, at time of writing, as well as possibly risky.
+     * See information in AndroidBridge.cpp regarding GetJNIForThread.
+     *
+     * Did I mention use of this function is discouraged?
+     */
+    boolean allowMultithread() default false;
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/mozglue/OptionalGeneratedParameter.java
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.mozglue;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Used to annotate parameters which are optional on the C++ side of the bridge. The annotation is
+ * used by the annotation processor to generate the appropriate C++ headers so calls into the Java
+ * method all have the optional params set to the default value.
+ * The default values are zero for numerical types, false for booleans, "" for strings, and null
+ * for all other reference types.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface OptionalGeneratedParameter {}
--- a/mobile/android/base/util/Clipboard.java
+++ b/mobile/android/base/util/Clipboard.java
@@ -4,16 +4,18 @@
 
 package org.mozilla.gecko.util;
 
 import android.content.ClipData;
 import android.content.Context;
 import android.os.Build;
 import android.util.Log;
 
+import org.mozilla.gecko.mozglue.GeneratableAndroidBridgeTarget;
+
 import java.util.concurrent.SynchronousQueue;
 
 public final class Clipboard {
     private static Context mContext;
     private final static String LOG_TAG = "Clipboard";
     private final static SynchronousQueue<String> sClipboardQueue = new SynchronousQueue<String>();
 
     private Clipboard() {
@@ -22,16 +24,17 @@ public final class Clipboard {
     public static void init(Context c) {
         if (mContext != null) {
             Log.w(LOG_TAG, "Clipboard.init() called twice!");
             return;
         }
         mContext = c;
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "GetClipboardTextWrapper")
     public static String getText() {
         // If we're on the UI thread or the background thread, we have a looper on the thread
         // and can just call this directly. For any other threads, post the call to the
         // background thread.
 
         if (ThreadUtils.isOnUiThread() || ThreadUtils.isOnBackgroundThread()) {
             return getClipboardTextImpl();
         }
@@ -47,16 +50,17 @@ public final class Clipboard {
         });
         try {
             return sClipboardQueue.take();
         } catch (InterruptedException ie) {
             return "";
         }
     }
 
+    @GeneratableAndroidBridgeTarget(stubName = "SetClipboardText")
     public static void setText(final CharSequence text) {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             @SuppressWarnings("deprecation")
             public void run() {
                 if (Build.VERSION.SDK_INT >= 11) {
                     android.content.ClipboardManager cm = getClipboardManager11(mContext);
                     ClipData clip = ClipData.newPlainText("Text", text);