Bug 695448 - Implement a Java compositor, and use it to scroll. r=?

This commit is contained in:
Patrick Walton 2011-11-09 17:39:29 -08:00
parent 3b6926c9b5
commit d20e174b9a
42 changed files with 4001 additions and 1458 deletions

View File

@ -40,6 +40,14 @@
package org.mozilla.gecko;
import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
import org.mozilla.gecko.gfx.IntRect;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.PlaceholderLayerClient;
import org.mozilla.gecko.Tab.HistoryEntry;
import java.io.*;
import java.util.*;
import java.util.zip.*;
@ -64,6 +72,7 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.BitmapDrawable;
import android.widget.*;
import android.hardware.*;
import android.location.*;
import android.util.*;
import android.net.*;
@ -76,7 +85,7 @@ import android.content.SharedPreferences.*;
import dalvik.system.*;
abstract public class GeckoApp
extends Activity implements GeckoEventListener
extends Activity implements GeckoEventListener, SensorEventListener, LocationListener
{
private static final String LOG_NAME = "GeckoApp";
@ -88,7 +97,6 @@ abstract public class GeckoApp
private LinearLayout mMainLayout;
private RelativeLayout mGeckoLayout;
public static GeckoSurfaceView surfaceView;
public static SurfaceView cameraView;
public static GeckoApp mAppContext;
public static boolean mFullScreen = false;
@ -103,6 +111,10 @@ abstract public class GeckoApp
private static boolean sIsGeckoReady = false;
private IntentFilter mBatteryFilter;
private BroadcastReceiver mBatteryReceiver;
private Geocoder mGeocoder;
private Address mLastGeoAddress;
private static LayerController mLayerController;
private static GeckoSoftwareLayerClient mSoftwareLayerClient;
boolean mUserDefinedProfile = false;
public interface OnTabsChangedListener {
@ -124,8 +136,8 @@ abstract public class GeckoApp
static Vector<ExtraMenuItem> sExtraMenuItems = new Vector<ExtraMenuItem>();
enum LaunchState {Launching, WaitButton,
Launched, GeckoRunning, GeckoExiting};
public enum LaunchState {Launching, WaitButton,
Launched, GeckoRunning, GeckoExiting};
private static LaunchState sLaunchState = LaunchState.Launching;
private static boolean sTryCatchAttached = false;
@ -133,7 +145,7 @@ abstract public class GeckoApp
private static final int AWESOMEBAR_REQUEST = 2;
private static final int CAMERA_CAPTURE_REQUEST = 3;
static boolean checkLaunchState(LaunchState checkState) {
public static boolean checkLaunchState(LaunchState checkState) {
synchronized(sLaunchState) {
return sLaunchState == checkState;
}
@ -539,28 +551,43 @@ abstract public class GeckoApp
}
}
public String getStartupBitmapFilePath() {
File file = new File(Environment.getExternalStorageDirectory(),
"lastScreen.png");
return file.toString();
}
private void rememberLastScreen(boolean sync) {
if (mUserDefinedProfile)
return;
if (surfaceView == null)
return;
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab == null)
return;
Tab.HistoryEntry he = tab.getLastHistoryEntry();
if (he != null) {
SharedPreferences prefs = getSharedPreferences("GeckoApp", MODE_PRIVATE);
Editor editor = prefs.edit();
editor.putString("last-uri", he.mUri);
editor.putString("last-title", he.mTitle);
HistoryEntry lastHistoryEntry = tab.getLastHistoryEntry();
if (lastHistoryEntry == null)
return;
Log.i(LOG_NAME, "Saving:: " + he.mUri + " " + he.mTitle);
editor.commit();
surfaceView.saveLast(sync);
}
SharedPreferences prefs = getSharedPreferences("GeckoApp", 0);
Editor editor = prefs.edit();
String uri = lastHistoryEntry.mUri;
String title = lastHistoryEntry.mTitle;
editor.putString("last-uri", uri);
editor.putString("last-title", title);
Log.i(LOG_NAME, "Saving:: " + uri + " " + title);
editor.commit();
GeckoEvent event = new GeckoEvent();
event.mType = GeckoEvent.SAVE_STATE;
event.mCharacters = getStartupBitmapFilePath();
if (sync)
GeckoAppShell.sendEventToGeckoSync(event);
else
GeckoAppShell.sendEventToGecko(event);
}
private void loadFavicon(final Tab tab) {
@ -714,6 +741,7 @@ abstract public class GeckoApp
final int tabId = message.getInt("tabID");
final String uri = message.getString("uri");
final String title = message.getString("title");
final JSONObject jsonPageSize = message.getJSONObject("pageSize");
final CharSequence titleText = title;
handleContentLoaded(tabId, uri, title);
Log.i(LOG_NAME, "URI - " + uri + ", title - " + title);
@ -787,6 +815,12 @@ abstract public class GeckoApp
sMenu.findItem(R.id.preferences).setEnabled(true);
}
});
} else if (event.equals("PanZoom:Ack")) {
final IntRect rect = new IntRect(message.getJSONObject("rect"));
mSoftwareLayerClient.jsPanZoomCompleted(rect);
} else if (event.equals("PanZoom:Resize")) {
final IntSize size = new IntSize(message.getJSONObject("size"));
mSoftwareLayerClient.setPageSize(size);
} else if (event.equals("ToggleChrome:Hide")) {
mMainHandler.post(new Runnable() {
public void run() {
@ -920,7 +954,6 @@ abstract public class GeckoApp
public void run() {
if (Tabs.getInstance().isSelectedTab(tab))
mBrowserToolbar.setProgressVisibility(false);
surfaceView.hideStartupBitmap();
onTabsChanged();
}
});
@ -1085,17 +1118,33 @@ abstract public class GeckoApp
cameraView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
if (surfaceView == null) {
surfaceView = new GeckoSurfaceView(this);
mGeckoLayout.addView(surfaceView);
} else if (mGeckoLayout.getChildCount() == 0) {
//surfaceView still holds to the old one during rotation. re-add it to new activity
((ViewGroup) surfaceView.getParent()).removeAllViews();
mGeckoLayout.addView(surfaceView);
if (mLayerController == null) {
/*
* Create a layer client so that Gecko will have a buffer to draw into, but don't hook
* it up to the layer controller yet.
*/
mSoftwareLayerClient = new GeckoSoftwareLayerClient(this);
/*
* Hook a placeholder layer client up to the layer controller so that the user can pan
* and zoom a cached screenshot of the previous page. This call will return null if
* there is no cached screenshot; in that case, we have no choice but to display a
* checkerboard.
*
* TODO: Fall back to a built-in screenshot of the Fennec Start page for a nice first-
* run experience, perhaps?
*/
PlaceholderLayerClient placeholderClient = mUserDefinedProfile ?
null : PlaceholderLayerClient.createInstance(this);
if (placeholderClient != null) {
mLayerController = new LayerController(this, placeholderClient);
placeholderClient.init();
} else {
mLayerController = new LayerController(this, null);
}
mGeckoLayout.addView(mLayerController.getView());
}
if (!mUserDefinedProfile)
surfaceView.loadStartupBitmap();
Log.w(LOGTAG, "zerdatime " + new Date().getTime() + " - UI almost up");
@ -1140,6 +1189,8 @@ abstract public class GeckoApp
GeckoAppShell.registerGeckoEventListener("Preferences:Data", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("Gecko:Ready", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("Toast:Show", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("PanZoom:Ack", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("PanZoom:Resize", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("ToggleChrome:Hide", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("ToggleChrome:Show", GeckoApp.mAppContext);
@ -1388,7 +1439,7 @@ abstract public class GeckoApp
Intent intent = new Intent(action);
intent.setClassName(getPackageName(),
getPackageName() + ".Restarter");
addEnvToIntent(intent);
/* TODO: addEnvToIntent(intent); */
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Log.i(LOG_NAME, intent.toString());
@ -1660,4 +1711,89 @@ abstract public class GeckoApp
GeckoAppShell.sendEventToGecko(new GeckoEvent("Tab:Load", url));
}
}
public GeckoSoftwareLayerClient getSoftwareLayerClient() { return mSoftwareLayerClient; }
public LayerController getLayerController() { return mLayerController; }
// accelerometer
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
}
public void onSensorChanged(SensorEvent event)
{
Log.w(LOGTAG, "onSensorChanged "+event);
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
}
private class GeocoderTask extends AsyncTask<Location, Void, Void> {
protected Void doInBackground(Location... location) {
try {
List<Address> addresses = mGeocoder.getFromLocation(location[0].getLatitude(),
location[0].getLongitude(), 1);
// grab the first address. in the future,
// may want to expose multiple, or filter
// for best.
mLastGeoAddress = addresses.get(0);
GeckoAppShell.sendEventToGecko(new GeckoEvent(location[0], mLastGeoAddress));
} catch (Exception e) {
Log.w(LOGTAG, "GeocoderTask "+e);
}
return null;
}
}
// geolocation
public void onLocationChanged(Location location)
{
Log.w(LOGTAG, "onLocationChanged "+location);
if (mGeocoder == null)
mGeocoder = new Geocoder(mLayerController.getView().getContext(), Locale.getDefault());
if (mLastGeoAddress == null) {
new GeocoderTask().execute(location);
}
else {
float[] results = new float[1];
Location.distanceBetween(location.getLatitude(),
location.getLongitude(),
mLastGeoAddress.getLatitude(),
mLastGeoAddress.getLongitude(),
results);
// pfm value. don't want to slam the
// geocoder with very similar values, so
// only call after about 100m
if (results[0] > 100)
new GeocoderTask().execute(location);
}
GeckoAppShell.sendEventToGecko(new GeckoEvent(location, mLastGeoAddress));
}
public void onProviderDisabled(String provider)
{
}
public void onProviderEnabled(String provider)
{
}
public void onStatusChanged(String provider, int status, Bundle extras)
{
}
public void connectGeckoLayerClient() {
new Timer("Gecko Wait").schedule(new TimerTask() {
public void run() {
GeckoApp.mAppContext.runOnUiThread(new Runnable() {
public void run() {
LayerController layerController = getLayerController();
layerController.setLayerClient(mSoftwareLayerClient);
mSoftwareLayerClient.init(); /* Attaches the new root layer. */
GeckoAppShell.scheduleRedraw();
}
});
}
}, 3000);
}
}

View File

@ -38,6 +38,12 @@
package org.mozilla.gecko;
import org.mozilla.gecko.gfx.FloatPoint;
import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
import org.mozilla.gecko.gfx.IntPoint;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.LayerView;
import java.io.*;
import java.lang.reflect.*;
import java.nio.*;
@ -90,15 +96,11 @@ public class GeckoAppShell
static private boolean gRestartScheduled = false;
static private PromptService gPromptService = null;
static private final Timer mIMETimer = new Timer();
static private GeckoInputConnection mInputConnection = null;
static private final HashMap<Integer, AlertNotification>
mAlertNotifications = new HashMap<Integer, AlertNotification>();
static private final int NOTIFY_IME_RESETINPUTSTATE = 0;
static private final int NOTIFY_IME_SETOPENSTATE = 1;
static private final int NOTIFY_IME_CANCELCOMPOSITION = 2;
static private final int NOTIFY_IME_FOCUSCHANGE = 3;
/* Keep in sync with constants found here:
http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
*/
@ -118,7 +120,8 @@ public class GeckoAppShell
public static native void nativeRun(String args);
// helper methods
public static native void setSurfaceView(GeckoSurfaceView sv);
// public static native void setSurfaceView(GeckoSurfaceView sv);
public static native void setSoftwareLayerClient(GeckoSoftwareLayerClient client);
public static native void putenv(String map);
public static native void onResume();
public static native void onLowMemory();
@ -414,8 +417,8 @@ public class GeckoAppShell
// run gecko -- it will spawn its own thread
GeckoAppShell.nativeInit();
// Tell Gecko where the target surface view is for rendering
GeckoAppShell.setSurfaceView(GeckoApp.surfaceView);
// Tell Gecko where the target byte buffer is for rendering
GeckoAppShell.setSoftwareLayerClient(GeckoApp.mAppContext.getSoftwareLayerClient());
// First argument is the .apk path
String combinedArgs = apkPath + " -greomni " + apkPath;
@ -424,10 +427,53 @@ public class GeckoAppShell
if (url != null)
combinedArgs += " -remote " + url;
/* TODO: Is this complexity necessary? */
new Timer("Gecko Setup").schedule(new TimerTask() {
public void run() {
GeckoApp.mAppContext.runOnUiThread(new Runnable() {
public void run() {
geckoLoaded();
}
});
}
}, 0);
// and go
GeckoAppShell.nativeRun(combinedArgs);
}
// Called on the UI thread after Gecko loads.
private static void geckoLoaded() {
GeckoApp.mAppContext.connectGeckoLayerClient();
final LayerController layerController = GeckoApp.mAppContext.getLayerController();
LayerView v = layerController.getView();
mInputConnection = new GeckoInputConnection(v);
v.setInputConnectionHandler(mInputConnection);
layerController.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View view, MotionEvent event) {
float origX = event.getX();
float origY = event.getY();
/* Transform the point to the layer offset. */
FloatPoint eventPoint = new FloatPoint(origX, origY);
FloatPoint geckoPoint = layerController.convertViewPointToLayerPoint(eventPoint);
event.setLocation((int)Math.round(geckoPoint.x), (int)Math.round(geckoPoint.y));
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
/* Restore the view coordinates in case the caller further processes this event */
event.setLocation(origX, origY);
return true;
}
});
GeckoEvent event = new GeckoEvent(GeckoEvent.SIZE_CHANGED,
LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT,
LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT);
GeckoAppShell.sendEventToGecko(event);
}
private static GeckoEvent mLastDrawEvent;
private static void sendPendingEventsToGecko() {
@ -440,7 +486,7 @@ public class GeckoAppShell
}
public static void sendEventToGecko(GeckoEvent e) {
if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
if (GeckoApp.mAppContext.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
notifyGeckoOfEvent(e);
} else {
gPendingEvents.addLast(e);
@ -460,145 +506,24 @@ public class GeckoAppShell
*/
public static void scheduleRedraw() {
// Redraw everything
scheduleRedraw(0, -1, -1, -1, -1);
Rect rect = new Rect(0, 0, LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT);
GeckoEvent event = new GeckoEvent(GeckoEvent.DRAW, rect);
event.mNativeWindow = 0;
sendEventToGecko(event);
}
public static void scheduleRedraw(int nativeWindow, int x, int y, int w, int h) {
GeckoEvent e;
if (x == -1) {
e = new GeckoEvent(GeckoEvent.DRAW, null);
} else {
e = new GeckoEvent(GeckoEvent.DRAW, new Rect(x, y, w, h));
}
e.mNativeWindow = nativeWindow;
sendEventToGecko(e);
}
/* Delay updating IME states (see bug 573800) */
private static final class IMEStateUpdater extends TimerTask
{
static private IMEStateUpdater instance;
private boolean mEnable, mReset;
static private IMEStateUpdater getInstance() {
if (instance == null) {
instance = new IMEStateUpdater();
mIMETimer.schedule(instance, 200);
}
return instance;
}
static public synchronized void enableIME() {
getInstance().mEnable = true;
}
static public synchronized void resetIME() {
getInstance().mReset = true;
}
public void run() {
synchronized(IMEStateUpdater.class) {
instance = null;
}
InputMethodManager imm = (InputMethodManager)
GeckoApp.surfaceView.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (imm == null)
return;
if (mReset)
imm.restartInput(GeckoApp.surfaceView);
if (!mEnable)
return;
int state = GeckoApp.surfaceView.mIMEState;
if (state != GeckoSurfaceView.IME_STATE_DISABLED &&
state != GeckoSurfaceView.IME_STATE_PLUGIN)
imm.showSoftInput(GeckoApp.surfaceView, 0);
else
imm.hideSoftInputFromWindow(
GeckoApp.surfaceView.getWindowToken(), 0);
}
}
public static void notifyIME(int type, int state) {
if (GeckoApp.surfaceView == null)
return;
switch (type) {
case NOTIFY_IME_RESETINPUTSTATE:
// Composition event is already fired from widget.
// So reset IME flags.
GeckoApp.surfaceView.inputConnection.reset();
// Don't use IMEStateUpdater for reset.
// Because IME may not work showSoftInput()
// after calling restartInput() immediately.
// So we have to call showSoftInput() delay.
InputMethodManager imm = (InputMethodManager)
GeckoApp.surfaceView.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (imm == null) {
// no way to reset IME status directly
IMEStateUpdater.resetIME();
} else {
imm.restartInput(GeckoApp.surfaceView);
}
// keep current enabled state
IMEStateUpdater.enableIME();
break;
case NOTIFY_IME_CANCELCOMPOSITION:
IMEStateUpdater.resetIME();
break;
case NOTIFY_IME_FOCUSCHANGE:
IMEStateUpdater.resetIME();
break;
}
mInputConnection.notifyIME(type, state);
}
public static void notifyIMEEnabled(int state, String typeHint,
String actionHint, boolean landscapeFS)
{
if (GeckoApp.surfaceView == null)
return;
/* When IME is 'disabled', IME processing is disabled.
In addition, the IME UI is hidden */
GeckoApp.surfaceView.mIMEState = state;
GeckoApp.surfaceView.mIMETypeHint = typeHint;
GeckoApp.surfaceView.mIMEActionHint = actionHint;
GeckoApp.surfaceView.mIMELandscapeFS = landscapeFS;
IMEStateUpdater.enableIME();
String actionHint, boolean landscapeFS) {
mInputConnection.notifyIMEEnabled(state, typeHint, actionHint, landscapeFS);
}
public static void notifyIMEChange(String text, int start, int end, int newEnd) {
if (GeckoApp.surfaceView == null ||
GeckoApp.surfaceView.inputConnection == null)
return;
InputMethodManager imm = (InputMethodManager)
GeckoApp.surfaceView.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (imm == null)
return;
// Log.d("GeckoAppJava", String.format("IME: notifyIMEChange: t=%s s=%d ne=%d oe=%d",
// text, start, newEnd, end));
if (newEnd < 0)
GeckoApp.surfaceView.inputConnection.notifySelectionChange(
imm, start, end);
else
GeckoApp.surfaceView.inputConnection.notifyTextChange(
imm, text, start, end, newEnd);
mInputConnection.notifyIMEChange(text, start, end, newEnd);
}
private static CountDownLatch sGeckoPendingAcks = null;
@ -627,8 +552,8 @@ public class GeckoAppShell
static Sensor gOrientationSensor = null;
public static void enableDeviceMotion(boolean enable) {
SensorManager sm = (SensorManager)
GeckoApp.surfaceView.getContext().getSystemService(Context.SENSOR_SERVICE);
LayerView v = GeckoApp.mAppContext.getLayerController().getView();
SensorManager sm = (SensorManager) v.getContext().getSystemService(Context.SENSOR_SERVICE);
if (gAccelerometerSensor == null || gOrientationSensor == null) {
gAccelerometerSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
@ -637,23 +562,24 @@ public class GeckoAppShell
if (enable) {
if (gAccelerometerSensor != null)
sm.registerListener(GeckoApp.surfaceView, gAccelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
sm.registerListener(GeckoApp.mAppContext, gAccelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
if (gOrientationSensor != null)
sm.registerListener(GeckoApp.surfaceView, gOrientationSensor, SensorManager.SENSOR_DELAY_GAME);
sm.registerListener(GeckoApp.mAppContext, gOrientationSensor, SensorManager.SENSOR_DELAY_GAME);
} else {
if (gAccelerometerSensor != null)
sm.unregisterListener(GeckoApp.surfaceView, gAccelerometerSensor);
sm.unregisterListener(GeckoApp.mAppContext, gAccelerometerSensor);
if (gOrientationSensor != null)
sm.unregisterListener(GeckoApp.surfaceView, gOrientationSensor);
sm.unregisterListener(GeckoApp.mAppContext, gOrientationSensor);
}
}
public static void enableLocation(final boolean enable) {
getMainHandler().post(new Runnable() {
public void run() {
GeckoSurfaceView view = GeckoApp.surfaceView;
LayerView v = GeckoApp.mAppContext.getLayerController().getView();
LocationManager lm = (LocationManager)
view.getContext().getSystemService(Context.LOCATION_SERVICE);
GeckoApp.mAppContext.getSystemService(Context.LOCATION_SERVICE);
if (enable) {
Criteria crit = new Criteria();
@ -665,11 +591,11 @@ public class GeckoAppShell
Looper l = Looper.getMainLooper();
Location loc = lm.getLastKnownLocation(provider);
if (loc != null) {
view.onLocationChanged(loc);
GeckoApp.mAppContext.onLocationChanged(loc);
}
lm.requestLocationUpdates(provider, 100, (float).5, view, l);
lm.requestLocationUpdates(provider, 100, (float).5, GeckoApp.mAppContext, l);
} else {
lm.removeUpdates(view);
lm.removeUpdates(GeckoApp.mAppContext);
}
}
});
@ -680,24 +606,19 @@ public class GeckoAppShell
}
public static void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
GeckoApp.surfaceView.inputConnection.mSelectionStart = selectionStart;
GeckoApp.surfaceView.inputConnection.mSelectionLength = selectionLength;
try {
GeckoApp.surfaceView.inputConnection.mQueryResult.put(result);
} catch (InterruptedException e) {
}
mInputConnection.returnIMEQueryResult(result, selectionStart, selectionLength);
}
static void onAppShellReady()
{
// mLaunchState can only be Launched at this point
GeckoApp.setLaunchState(GeckoApp.LaunchState.GeckoRunning);
GeckoApp.mAppContext.setLaunchState(GeckoApp.LaunchState.GeckoRunning);
sendPendingEventsToGecko();
}
static void onXreExit() {
// mLaunchState can only be Launched or GeckoRunning at this point
GeckoApp.setLaunchState(GeckoApp.LaunchState.GeckoExiting);
GeckoApp.mAppContext.setLaunchState(GeckoApp.LaunchState.GeckoExiting);
Log.i("GeckoAppJava", "XRE exited");
if (gRestartScheduled) {
GeckoApp.mAppContext.doRestart();
@ -761,8 +682,7 @@ public class GeckoAppShell
}
static String[] getHandlersForIntent(Intent intent) {
PackageManager pm =
GeckoApp.surfaceView.getContext().getPackageManager();
PackageManager pm = GeckoApp.mAppContext.getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
int numAttr = 4;
String[] ret = new String[list.size() * numAttr];
@ -861,7 +781,7 @@ public class GeckoAppShell
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
try {
GeckoApp.surfaceView.getContext().startActivity(intent);
GeckoApp.mAppContext.startActivity(intent);
return true;
} catch(ActivityNotFoundException e) {
return false;
@ -880,7 +800,7 @@ public class GeckoAppShell
getHandler().post(new Runnable() {
@SuppressWarnings("deprecation")
public void run() {
Context context = GeckoApp.surfaceView.getContext();
Context context = GeckoApp.mAppContext;
String text = null;
if (android.os.Build.VERSION.SDK_INT >= 11) {
android.content.ClipboardManager cm = (android.content.ClipboardManager)
@ -913,7 +833,7 @@ public class GeckoAppShell
getHandler().post(new Runnable() {
@SuppressWarnings("deprecation")
public void run() {
Context context = GeckoApp.surfaceView.getContext();
Context context = GeckoApp.mAppContext;
if (android.os.Build.VERSION.SDK_INT >= 11) {
android.content.ClipboardManager cm = (android.content.ClipboardManager)
context.getSystemService(Context.CLIPBOARD_SERVICE);
@ -1057,21 +977,19 @@ public class GeckoAppShell
}
public static void performHapticFeedback(boolean aIsLongPress) {
GeckoApp.surfaceView.
performHapticFeedback(aIsLongPress ?
HapticFeedbackConstants.LONG_PRESS :
HapticFeedbackConstants.VIRTUAL_KEY);
// TODO
}
public static void showInputMethodPicker() {
InputMethodManager imm = (InputMethodManager) GeckoApp.surfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
InputMethodManager imm = (InputMethodManager)
GeckoApp.mAppContext.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showInputMethodPicker();
}
public static void setKeepScreenOn(final boolean on) {
GeckoApp.mAppContext.runOnUiThread(new Runnable() {
public void run() {
GeckoApp.surfaceView.setKeepScreenOn(on);
// TODO
}
});
}
@ -1245,7 +1163,7 @@ public class GeckoAppShell
}
public static void scanMedia(String aFile, String aMimeType) {
Context context = GeckoApp.surfaceView.getContext();
Context context = GeckoApp.mAppContext;
GeckoMediaScannerClient client = new GeckoMediaScannerClient(context, aFile, aMimeType);
}
@ -1257,7 +1175,7 @@ public class GeckoAppShell
if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.')
aExt = aExt.substring(1);
PackageManager pm = GeckoApp.surfaceView.getContext().getPackageManager();
PackageManager pm = GeckoApp.mAppContext.getPackageManager();
Drawable icon = getDrawableForExtension(pm, aExt);
if (icon == null) {
// Use a generic icon
@ -1662,8 +1580,11 @@ public class GeckoAppShell
if (!accessibilityManager.isEnabled())
return;
LayerController layerController = GeckoApp.mAppContext.getLayerController();
LayerView layerView = layerController.getView();
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.setClassName(GeckoApp.surfaceView.getClass().getName() + "$" + role);
event.setClassName(layerView.getClass().getName() + "$" + role);
event.setPackageName(GeckoApp.mAppContext.getPackageName());
event.setEnabled(enabled);
event.setChecked(checked);

View File

@ -203,14 +203,14 @@ public class GeckoEvent {
rangeForeColor, rangeBackColor);
}
public GeckoEvent(int etype, Rect dirty) {
public GeckoEvent(int etype, Rect rect) {
if (etype != DRAW) {
mType = INVALID;
return;
}
mType = etype;
mRect = dirty;
mRect = rect;
}
public GeckoEvent(int etype, int w, int h, int screenw, int screenh) {

View File

@ -42,6 +42,10 @@ import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import org.mozilla.gecko.gfx.InputConnectionHandler;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.os.*;
import android.app.*;
import android.text.*;
@ -51,11 +55,15 @@ import android.view.inputmethod.*;
import android.content.*;
import android.R;
import android.text.method.TextKeyListener;
import android.text.method.KeyListener;
import android.util.*;
public class GeckoInputConnection
extends BaseInputConnection
implements TextWatcher
implements TextWatcher, InputConnectionHandler
{
private class ChangeNotification {
public String mText;
@ -81,25 +89,31 @@ public class GeckoInputConnection
public GeckoInputConnection (View targetView) {
super(targetView, true);
mQueryResult = new SynchronousQueue<String>();
mEditableFactory = Editable.Factory.getInstance();
initEditable("");
mIMEState = IME_STATE_DISABLED;
mIMETypeHint = "";
mIMEActionHint = "";
}
@Override
public boolean beginBatchEdit() {
//Log.d("GeckoAppJava", "IME: beginBatchEdit");
Log.d("GeckoAppJava", "IME: beginBatchEdit");
mBatchMode = true;
return true;
}
@Override
public boolean commitCompletion(CompletionInfo text) {
//Log.d("GeckoAppJava", "IME: commitCompletion");
Log.d("GeckoAppJava", "IME: commitCompletion");
return commitText(text.getText(), 1);
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
//Log.d("GeckoAppJava", "IME: commitText");
Log.d("GeckoAppJava", "IME: commitText");
setComposingText(text, newCursorPosition);
finishComposingText();
@ -109,7 +123,7 @@ public class GeckoInputConnection
@Override
public boolean deleteSurroundingText(int leftLength, int rightLength) {
//Log.d("GeckoAppJava", "IME: deleteSurroundingText");
Log.d("GeckoAppJava", "IME: deleteSurroundingText");
if (leftLength == 0 && rightLength == 0)
return true;
@ -168,13 +182,13 @@ public class GeckoInputConnection
@Override
public boolean endBatchEdit() {
//Log.d("GeckoAppJava", "IME: endBatchEdit");
Log.d("GeckoAppJava", "IME: endBatchEdit");
mBatchMode = false;
if (!mBatchChanges.isEmpty()) {
InputMethodManager imm = (InputMethodManager)
GeckoApp.surfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
GeckoApp.mAppContext.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
for (ChangeNotification n : mBatchChanges) {
if (n.mText != null)
@ -190,7 +204,7 @@ public class GeckoInputConnection
@Override
public boolean finishComposingText() {
//Log.d("GeckoAppJava", "IME: finishComposingText");
Log.d("GeckoAppJava", "IME: finishComposingText");
if (mComposing) {
// Set style to none
@ -215,7 +229,7 @@ public class GeckoInputConnection
@Override
public int getCursorCapsMode(int reqModes) {
//Log.d("GeckoAppJava", "IME: getCursorCapsMode");
Log.d("GeckoAppJava", "IME: getCursorCapsMode");
return 0;
}
@ -230,7 +244,7 @@ public class GeckoInputConnection
@Override
public boolean performContextMenuAction(int id) {
//Log.d("GeckoAppJava", "IME: performContextMenuAction");
Log.d("GeckoAppJava", "IME: performContextMenuAction");
// First we need to ask Gecko to tell us the full contents of the
// text field we're about to operate on.
@ -290,7 +304,7 @@ public class GeckoInputConnection
if (!GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning))
return null;
//Log.d("GeckoAppJava", "IME: getExtractedText");
Log.d("GeckoAppJava", "IME: getExtractedText");
ExtractedText extract = new ExtractedText();
extract.flags = 0;
@ -342,7 +356,7 @@ public class GeckoInputConnection
@Override
public CharSequence getTextAfterCursor(int length, int flags) {
//Log.d("GeckoAppJava", "IME: getTextAfterCursor");
Log.d("GeckoAppJava", "IME: getTextAfterCursor");
GeckoAppShell.sendEventToGecko(
new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
@ -379,14 +393,14 @@ public class GeckoInputConnection
@Override
public CharSequence getTextBeforeCursor(int length, int flags) {
//Log.d("GeckoAppJava", "IME: getTextBeforeCursor");
Log.d("GeckoAppJava", "IME: getTextBeforeCursor");
return getTextAfterCursor(-length, flags);
}
@Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
//Log.d("GeckoAppJava", "IME: setComposingText");
Log.d("GeckoAppJava", "IME: setComposingText");
enableChangeNotifications();
@ -520,7 +534,7 @@ public class GeckoInputConnection
@Override
public boolean setComposingRegion(int start, int end) {
//Log.d("GeckoAppJava", "IME: setComposingRegion(start=" + start + ", end=" + end + ")");
Log.d("GeckoAppJava", "IME: setComposingRegion(start=" + start + ", end=" + end + ")");
if (start < 0 || end < start)
return true;
@ -554,7 +568,7 @@ public class GeckoInputConnection
@Override
public boolean setSelection(int start, int end) {
//Log.d("GeckoAppJava", "IME: setSelection");
Log.d("GeckoAppJava", "IME: setSelection");
if (mComposing) {
/* Translate to fake selection positions */
@ -602,8 +616,8 @@ public class GeckoInputConnection
public void notifyTextChange(InputMethodManager imm, String text,
int start, int oldEnd, int newEnd) {
// Log.d("GeckoAppShell", String.format("IME: notifyTextChange: text=%s s=%d ne=%d oe=%d",
// text, start, newEnd, oldEnd));
Log.d("GeckoAppShell", String.format("IME: notifyTextChange: text=%s s=%d ne=%d oe=%d",
text, start, newEnd, oldEnd));
if (!mChangeNotificationsEnabled)
return;
@ -616,8 +630,10 @@ public class GeckoInputConnection
// If there are pending changes, that means this text is not the most up-to-date version
// and we'll step on ourselves if we change the editable right now.
if (mNumPendingChanges == 0 && !text.contentEquals(GeckoApp.surfaceView.mEditable))
GeckoApp.surfaceView.setEditable(text);
View v = GeckoApp.mAppContext.getLayerController().getView();
if (mNumPendingChanges == 0 && !text.contentEquals(mEditable))
setEditable(text);
if (mUpdateRequest == null)
return;
@ -636,13 +652,12 @@ public class GeckoInputConnection
mUpdateExtract.text = text.substring(0, newEnd);
mUpdateExtract.startOffset = 0;
imm.updateExtractedText(GeckoApp.surfaceView,
mUpdateRequest.token, mUpdateExtract);
imm.updateExtractedText(v, mUpdateRequest.token, mUpdateExtract);
}
public void notifySelectionChange(InputMethodManager imm,
int start, int end) {
// Log.d("GeckoAppJava", String.format("IME: notifySelectionChange: s=%d e=%d", start, end));
Log.d("GeckoAppJava", String.format("IME: notifySelectionChange: s=%d e=%d", start, end));
if (!mChangeNotificationsEnabled)
return;
@ -652,22 +667,23 @@ public class GeckoInputConnection
return;
}
View v = GeckoApp.mAppContext.getLayerController().getView();
if (mComposing)
imm.updateSelection(GeckoApp.surfaceView,
mCompositionStart + mCompositionSelStart,
mCompositionStart + mCompositionSelStart + mCompositionSelLen,
mCompositionStart,
mCompositionStart + mComposingText.length());
imm.updateSelection(v,
mCompositionStart + mCompositionSelStart,
mCompositionStart + mCompositionSelStart + mCompositionSelLen,
mCompositionStart,
mCompositionStart + mComposingText.length());
else
imm.updateSelection(GeckoApp.surfaceView, start, end, -1, -1);
imm.updateSelection(v, start, end, -1, -1);
// We only change the selection if we are relatively sure that the text we have is
// up-to-date. Bail out if we are stil expecting changes.
if (mNumPendingChanges > 0)
return;
int maxLen = GeckoApp.surfaceView.mEditable.length();
Selection.setSelection(GeckoApp.surfaceView.mEditable,
int maxLen = mEditable.length();
Selection.setSelection(mEditable,
Math.min(start, maxLen),
Math.min(end, maxLen));
}
@ -684,8 +700,8 @@ public class GeckoInputConnection
// TextWatcher
public void onTextChanged(CharSequence s, int start, int before, int count)
{
// Log.d("GeckoAppShell", String.format("IME: onTextChanged: t=%s s=%d b=%d l=%d",
// s, start, before, count));
Log.d("GeckoAppShell", String.format("IME: onTextChanged: t=%s s=%d b=%d l=%d",
s, start, before, count));
mNumPendingChanges++;
GeckoAppShell.sendEventToGecko(
@ -732,6 +748,331 @@ public class GeckoInputConnection
mChangeNotificationsEnabled = true;
}
public InputConnection onCreateInputConnection(EditorInfo outAttrs)
{
Log.d("GeckoAppJava", "IME: handleCreateInputConnection called");
outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
outAttrs.actionLabel = null;
mKeyListener = TextKeyListener.getInstance();
if (mIMEState == IME_STATE_PASSWORD)
outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
else if (mIMETypeHint.equalsIgnoreCase("url"))
outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_URI;
else if (mIMETypeHint.equalsIgnoreCase("email"))
outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
else if (mIMETypeHint.equalsIgnoreCase("search"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
else if (mIMETypeHint.equalsIgnoreCase("tel"))
outAttrs.inputType = InputType.TYPE_CLASS_PHONE;
else if (mIMETypeHint.equalsIgnoreCase("number") ||
mIMETypeHint.equalsIgnoreCase("range"))
outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;
else if (mIMETypeHint.equalsIgnoreCase("datetime") ||
mIMETypeHint.equalsIgnoreCase("datetime-local"))
outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
InputType.TYPE_DATETIME_VARIATION_NORMAL;
else if (mIMETypeHint.equalsIgnoreCase("date"))
outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
InputType.TYPE_DATETIME_VARIATION_DATE;
else if (mIMETypeHint.equalsIgnoreCase("time"))
outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
InputType.TYPE_DATETIME_VARIATION_TIME;
if (mIMEActionHint.equalsIgnoreCase("go"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_GO;
else if (mIMEActionHint.equalsIgnoreCase("done"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
else if (mIMEActionHint.equalsIgnoreCase("next"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
else if (mIMEActionHint.equalsIgnoreCase("search"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
else if (mIMEActionHint.equalsIgnoreCase("send"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_SEND;
else if (mIMEActionHint != null && mIMEActionHint.length() != 0)
outAttrs.actionLabel = mIMEActionHint;
if (mIMELandscapeFS == false)
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
reset();
return this;
}
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
switch (event.getAction()) {
case KeyEvent.ACTION_DOWN:
return processKeyDown(keyCode, event, true);
case KeyEvent.ACTION_UP:
return processKeyUp(keyCode, event, true);
case KeyEvent.ACTION_MULTIPLE:
return onKeyMultiple(keyCode, event.getRepeatCount(), event);
}
return false;
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
return processKeyDown(keyCode, event, false);
}
private boolean processKeyDown(int keyCode, KeyEvent event, boolean isPreIme) {
switch (keyCode) {
case KeyEvent.KEYCODE_MENU:
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_SEARCH:
return false;
case KeyEvent.KEYCODE_DEL:
// See comments in GeckoInputConnection.onKeyDel
if (onKeyDel()) {
return true;
}
break;
case KeyEvent.KEYCODE_ENTER:
if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 &&
mIMEActionHint.equalsIgnoreCase("next"))
event = new KeyEvent(event.getAction(), KeyEvent.KEYCODE_TAB);
break;
default:
break;
}
if (isPreIme && mIMEState != IME_STATE_DISABLED &&
(event.getMetaState() & KeyEvent.META_ALT_ON) == 0)
// Let active IME process pre-IME key events
return false;
View v = GeckoApp.mAppContext.getLayerController().getView();
// KeyListener returns true if it handled the event for us.
if (mIMEState == IME_STATE_DISABLED ||
keyCode == KeyEvent.KEYCODE_ENTER ||
keyCode == KeyEvent.KEYCODE_DEL ||
(event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
!mKeyListener.onKeyDown(v, mEditable, keyCode, event))
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
return true;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
return processKeyUp(keyCode, event, false);
}
private boolean processKeyUp(int keyCode, KeyEvent event, boolean isPreIme) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_SEARCH:
case KeyEvent.KEYCODE_MENU:
return false;
default:
break;
}
if (isPreIme && mIMEState != IME_STATE_DISABLED &&
(event.getMetaState() & KeyEvent.META_ALT_ON) == 0)
// Let active IME process pre-IME key events
return false;
View v = GeckoApp.mAppContext.getLayerController().getView();
if (mIMEState == IME_STATE_DISABLED ||
keyCode == KeyEvent.KEYCODE_ENTER ||
keyCode == KeyEvent.KEYCODE_DEL ||
(event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
!mKeyListener.onKeyUp(v, mEditable, keyCode, event))
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
return true;
}
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
return true;
}
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
View v = GeckoApp.mAppContext.getLayerController().getView();
switch (keyCode) {
case KeyEvent.KEYCODE_MENU:
InputMethodManager imm = (InputMethodManager)
v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInputFromWindow(v.getWindowToken(),
imm.SHOW_FORCED, 0);
return true;
default:
break;
}
return false;
}
public void notifyIME(int type, int state) {
View v = GeckoApp.mAppContext.getLayerController().getView();
Log.d("GeckoAppJava", "notifyIME");
if (v == null)
return;
Log.d("GeckoAppJava", "notifyIME v!= null");
switch (type) {
case NOTIFY_IME_RESETINPUTSTATE:
Log.d("GeckoAppJava", "notifyIME = reset");
// Composition event is already fired from widget.
// So reset IME flags.
reset();
// Don't use IMEStateUpdater for reset.
// Because IME may not work showSoftInput()
// after calling restartInput() immediately.
// So we have to call showSoftInput() delay.
InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
// no way to reset IME status directly
IMEStateUpdater.resetIME();
} else {
imm.restartInput(v);
}
// keep current enabled state
IMEStateUpdater.enableIME();
break;
case NOTIFY_IME_CANCELCOMPOSITION:
Log.d("GeckoAppJava", "notifyIME = cancel");
IMEStateUpdater.resetIME();
break;
case NOTIFY_IME_FOCUSCHANGE:
Log.d("GeckoAppJava", "notifyIME = focus");
IMEStateUpdater.resetIME();
break;
}
}
public void notifyIMEEnabled(int state, String typeHint,
String actionHint, boolean landscapeFS)
{
View v = GeckoApp.mAppContext.getLayerController().getView();
if (v == null)
return;
/* When IME is 'disabled', IME processing is disabled.
In addition, the IME UI is hidden */
mIMEState = state;
mIMETypeHint = typeHint;
mIMEActionHint = actionHint;
mIMELandscapeFS = landscapeFS;
IMEStateUpdater.enableIME();
}
public void notifyIMEChange(String text, int start, int end, int newEnd) {
View v = GeckoApp.mAppContext.getLayerController().getView();
if (v == null)
return;
InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null)
return;
Log.d("GeckoAppJava", String.format("IME: notifyIMEChange: t=%s s=%d ne=%d oe=%d",
text, start, newEnd, end));
if (newEnd < 0)
notifySelectionChange(imm, start, end);
else
notifyTextChange(imm, text, start, end, newEnd);
}
public void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
mSelectionStart = selectionStart;
mSelectionLength = selectionLength;
try {
mQueryResult.put(result);
} catch (InterruptedException e) {}
}
static private final Timer mIMETimer = new Timer();
static private final int NOTIFY_IME_RESETINPUTSTATE = 0;
static private final int NOTIFY_IME_SETOPENSTATE = 1;
static private final int NOTIFY_IME_CANCELCOMPOSITION = 2;
static private final int NOTIFY_IME_FOCUSCHANGE = 3;
/* Delay updating IME states (see bug 573800) */
private static final class IMEStateUpdater extends TimerTask
{
static private IMEStateUpdater instance;
private boolean mEnable, mReset;
static private IMEStateUpdater getInstance() {
if (instance == null) {
instance = new IMEStateUpdater();
mIMETimer.schedule(instance, 200);
}
return instance;
}
static public synchronized void enableIME() {
getInstance().mEnable = true;
}
static public synchronized void resetIME() {
getInstance().mReset = true;
}
public void run() {
Log.d("GeckoAppJava", "IME: run()");
synchronized(IMEStateUpdater.class) {
instance = null;
}
View v = GeckoApp.mAppContext.getLayerController().getView();
Log.d("GeckoAppJava", "IME: v="+v);
InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null)
return;
if (mReset)
imm.restartInput(v);
if (!mEnable)
return;
if (mIMEState != IME_STATE_DISABLED &&
mIMEState != IME_STATE_PLUGIN)
imm.showSoftInput(v, 0);
else
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
}
public void setEditable(String contents)
{
mEditable.removeSpan(this);
mEditable.replace(0, mEditable.length(), contents);
mEditable.setSpan(this, 0, contents.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
Selection.setSelection(mEditable, contents.length());
}
public void initEditable(String contents)
{
mEditable = mEditableFactory.newEditable(contents);
mEditable.setSpan(this, 0, contents.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
Selection.setSelection(mEditable, contents.length());
}
// Is a composition active?
boolean mComposing;
// Composition text when a composition is active
@ -747,6 +1088,20 @@ public class GeckoInputConnection
// Number of in flight changes
int mNumPendingChanges;
// IME stuff
public static final int IME_STATE_DISABLED = 0;
public static final int IME_STATE_ENABLED = 1;
public static final int IME_STATE_PASSWORD = 2;
public static final int IME_STATE_PLUGIN = 3;
KeyListener mKeyListener;
Editable mEditable;
Editable.Factory mEditableFactory;
static int mIMEState;
static String mIMETypeHint;
static String mIMEActionHint;
static boolean mIMELandscapeFS;
private boolean mBatchMode;
private boolean mChangeNotificationsEnabled = true;

View File

@ -1,827 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Vladimir Vukicevic <vladimir@pobox.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.concurrent.atomic.*;
import java.util.zip.*;
import java.nio.*;
import android.os.*;
import android.app.*;
import android.text.*;
import android.text.method.*;
import android.view.*;
import android.view.inputmethod.*;
import android.content.*;
import android.graphics.*;
import android.widget.*;
import android.hardware.*;
import android.location.*;
import android.graphics.drawable.*;
import android.content.res.*;
import android.util.*;
/*
* GeckoSurfaceView implements a GL surface view,
* similar to GLSurfaceView. However, since we
* already have a thread for Gecko, we don't really want
* a separate renderer thread that GLSurfaceView provides.
*/
class GeckoSurfaceView
extends SurfaceView
implements SurfaceHolder.Callback, SensorEventListener, LocationListener
{
private static final String LOG_FILE_NAME = "GeckoSurfaceView";
public GeckoSurfaceView(Context context) {
super(context, null, android.R.style.Theme_Light_NoTitleBar);
getHolder().addCallback(this);
inputConnection = new GeckoInputConnection(this);
gestureScanner = new GeckoGestureDetector(context);
setFocusable(true);
setFocusableInTouchMode(true);
DisplayMetrics metrics = new DisplayMetrics();
GeckoApp.mAppContext.getWindowManager().
getDefaultDisplay().getMetrics(metrics);
mWidth = metrics.widthPixels;
mHeight = metrics.heightPixels;
mBufferWidth = 0;
mBufferHeight = 0;
mSurfaceLock = new ReentrantLock();
mEditableFactory = Editable.Factory.getInstance();
initEditable("");
mIMEState = IME_STATE_DISABLED;
mIMETypeHint = "";
mIMEActionHint = "";
}
protected void finalize() throws Throwable {
super.finalize();
}
/*
* Called on main thread
*/
public String getStartupBitmapFilePath() {
File file = new File(Environment.getExternalStorageDirectory(),
"lastScreen.png");
return file.toString();
}
public void hideStartupBitmap() {
Log.e(LOG_FILE_NAME, "!!! hideStartupBitmap !!!");
if (mShowingLoadScreen == false)
return;
mStartupBitmap = null;
mShowingLoadScreen = false;
surfaceCreated(getHolder());
surfaceChanged(getHolder(), mFormat, mWidth, mHeight);
}
public void showStartupBitmap() {
Log.e(LOG_FILE_NAME, "!!! showStartupBitmap !!!");
mShowingLoadScreen = true;
}
public void loadStartupBitmap() {
// This is blocking on the main thread and that is
// okay. we want to get this image in as soon as
// possible so that we can paint it to the screen.
String filePath = getStartupBitmapFilePath();
mStartupBitmap = BitmapFactory.decodeFile(filePath);
}
public void drawStartupBitmap(SurfaceHolder holder, int width, int height) {
Log.e(LOG_FILE_NAME, "!!! drawStartupBitmap !!!");
Canvas c = holder.lockCanvas();
if (c == null) {
Log.e(LOG_FILE_NAME, "!!! NO CANVAS !!!");
return;
}
if (mStartupBitmap == null) {
Resources res = getResources();
Drawable drawable = res.getDrawable(R.drawable.start);
drawable.setBounds(0, 0, width, height);
drawable.draw(c);
Paint paint = new Paint();
c.drawText("Place holder. Missing screenshot.", 10.0f, 20.0f, paint);
} else {
Drawable drawable = new BitmapDrawable(mStartupBitmap);
drawable.setBounds(0, 0, width, height);
drawable.draw(c);
}
holder.unlockCanvasAndPost(c);
}
public void draw(SurfaceHolder holder, ByteBuffer buffer) {
Log.e(LOG_FILE_NAME, "!!! draw1 !!!");
if (buffer == null || buffer.capacity() != (mWidth * mHeight * 2))
return;
synchronized (mSoftwareBuffer) {
if (buffer != mSoftwareBuffer || mSoftwareBufferCopy == null)
return;
Canvas c = holder.lockCanvas();
if (c == null)
return;
mSoftwareBufferCopy.copyPixelsFromBuffer(buffer);
c.drawBitmap(mSoftwareBufferCopy, 0, 0, null);
holder.unlockCanvasAndPost(c);
}
}
public void draw(SurfaceHolder holder, Bitmap bitmap) {
Log.e(LOG_FILE_NAME, "!!! draw2 !!!");
if (bitmap == null ||
bitmap.getWidth() != mWidth || bitmap.getHeight() != mHeight)
return;
synchronized (mSoftwareBitmap) {
if (bitmap != mSoftwareBitmap)
return;
Canvas c = holder.lockCanvas();
if (c == null)
return;
c.drawBitmap(bitmap, 0, 0, null);
holder.unlockCanvasAndPost(c);
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.i(LOG_FILE_NAME, "!!! surfaceChanged: fmt: " + format + " dim: " + width + " " + height);
mFormat = format;
mWidth = width;
mHeight = height;
if (mShowingLoadScreen) {
drawStartupBitmap(holder, width, height);
return;
}
// On pre-Honeycomb, force exactly one frame of the previous size
// to render because the surface change is only seen by GLES after we
// have swapped the back buffer (i.e. the buffer size only changes
// after the next swap buffer). We need to make sure Gecko's view
// resizes when Android's buffer resizes.
// In Honeycomb, the buffer size changes immediately, so rendering a
// frame of the previous size is unnecessary (and wrong).
if (mDrawMode == DRAW_GLES_2 &&
(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)) {
// When we get a surfaceChange event, we have 0 to n paint events
// waiting in the Gecko event queue. We will make the first
// succeed and the abort the others.
mDrawSingleFrame = true;
if (!mInDrawing) {
// Queue at least one paint event in case none are queued.
GeckoAppShell.scheduleRedraw();
}
GeckoAppShell.geckoEventSync();
mDrawSingleFrame = false;
mAbortDraw = false;
}
mSurfaceLock.lock();
if (mInDrawing) {
Log.w(LOG_FILE_NAME, "!! surfaceChanged while mInDrawing is true!");
}
boolean invalidSize;
if (width == 0 || height == 0) {
mSoftwareBitmap = null;
mSoftwareBuffer = null;
mSoftwareBufferCopy = null;
invalidSize = true;
} else {
invalidSize = false;
}
boolean doSyncDraw =
mDrawMode == DRAW_2D &&
!invalidSize &&
GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning);
mSyncDraw = doSyncDraw;
mSurfaceValid = true;
Log.i(LOG_FILE_NAME, "!! surfaceChanged: fmt: " + format + " dim: " + width + " " + height);
try {
DisplayMetrics metrics = new DisplayMetrics();
GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
GeckoEvent e = new GeckoEvent(GeckoEvent.SIZE_CHANGED, width, height,
metrics.widthPixels, metrics.heightPixels);
GeckoAppShell.sendEventToGecko(e);
} finally {
mSurfaceLock.unlock();
}
if (doSyncDraw) {
GeckoAppShell.scheduleRedraw();
Object syncDrawObject = null;
try {
syncDrawObject = mSyncDraws.take();
} catch (InterruptedException ie) {
Log.e(LOG_FILE_NAME, "!! Threw exception while getting sync draw bitmap/buffer: ", ie);
}
if (syncDrawObject != null) {
if (syncDrawObject instanceof Bitmap)
draw(holder, (Bitmap)syncDrawObject);
else
draw(holder, (ByteBuffer)syncDrawObject);
} else {
Log.e(LOG_FILE_NAME, "!! Synchronised draw object is null");
}
} else if (!mShowingLoadScreen) {
// Make sure a frame is drawn before we return
// otherwise we see artifacts or a black screen
GeckoAppShell.scheduleRedraw();
GeckoAppShell.geckoEventSync();
}
// if the surface changed size and we have the soft keyboard up, make sure
// the focused input field is still in view or it might get hidden behind the
// keyboard and be really hard to use
if (mIMEState == IME_STATE_ENABLED) {
GeckoAppShell.sendEventToGecko(new GeckoEvent("ScrollTo:FocusedInput", null));
}
}
public void surfaceCreated(SurfaceHolder holder) {
// Delay sending this event if we are painting the
// load screen. The native access paint path will
// paint directly to the screen and we will see a
// black screen while content is initally being
// drawn.
if (!mShowingLoadScreen) {
Log.i(LOG_FILE_NAME, "!! surface created");
GeckoEvent e = new GeckoEvent(GeckoEvent.SURFACE_CREATED);
GeckoAppShell.sendEventToGecko(e);
}
}
public void saveLast(boolean sync) {
Log.i(LOG_FILE_NAME, "!! save last");
GeckoEvent event = new GeckoEvent();
event.mType = GeckoEvent.SAVE_STATE;
event.mCharacters = getStartupBitmapFilePath();
if (sync)
GeckoAppShell.sendEventToGeckoSync(event);
else
GeckoAppShell.sendEventToGecko(event);
}
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i(LOG_FILE_NAME, "!! surface destroyed");
mSurfaceValid = false;
mSoftwareBuffer = null;
mSoftwareBufferCopy = null;
mSoftwareBitmap = null;
GeckoEvent e = new GeckoEvent(GeckoEvent.SURFACE_DESTROYED);
if (mDrawMode == DRAW_GLES_2) {
// Ensure GL cleanup occurs before we return.
GeckoAppShell.sendEventToGeckoSync(e);
} else {
GeckoAppShell.sendEventToGecko(e);
}
}
public Bitmap getSoftwareDrawBitmap() {
if (mSoftwareBitmap == null ||
mSoftwareBitmap.getHeight() != mHeight ||
mSoftwareBitmap.getWidth() != mWidth) {
mSoftwareBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565);
}
mDrawMode = DRAW_2D;
return mSoftwareBitmap;
}
public ByteBuffer getSoftwareDrawBuffer() {
// We store pixels in 565 format, so two bytes per pixel (explaining
// the * 2 in the following check/allocation)
if (mSoftwareBuffer == null ||
mSoftwareBuffer.capacity() != (mWidth * mHeight * 2)) {
mSoftwareBuffer = ByteBuffer.allocateDirect(mWidth * mHeight * 2);
}
if (mSoftwareBufferCopy == null ||
mSoftwareBufferCopy.getHeight() != mHeight ||
mSoftwareBufferCopy.getWidth() != mWidth) {
mSoftwareBufferCopy = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565);
}
mDrawMode = DRAW_2D;
return mSoftwareBuffer;
}
public Surface getSurface() {
return getHolder().getSurface();
}
/*
* Called on Gecko thread
*/
public static final int DRAW_ERROR = 0;
public static final int DRAW_GLES_2 = 1;
public static final int DRAW_2D = 2;
// Drawing is disable when the surface buffer
// has changed size but we haven't yet processed the
// resize event.
public static final int DRAW_DISABLED = 3;
public int beginDrawing() {
if (mInDrawing) {
Log.e(LOG_FILE_NAME, "!! Recursive beginDrawing call!");
return DRAW_ERROR;
}
// Once we drawn our first frame after resize we can ignore
// the other draw events until we handle the resize events.
if (mAbortDraw) {
return DRAW_DISABLED;
}
/* Grab the lock, which we'll hold while we're drawing.
* It gets released in endDrawing(), and is also used in surfaceChanged
* to make sure that we don't change our surface details while
* we're in the middle of drawing (and especially in the middle of
* executing beginDrawing/endDrawing).
*
* We might not need to hold this lock in between
* beginDrawing/endDrawing, and might just be able to make
* surfaceChanged, beginDrawing, and endDrawing synchronized,
* but this way is safer for now.
*/
mSurfaceLock.lock();
if (!mSurfaceValid) {
Log.e(LOG_FILE_NAME, "!! Surface not valid");
mSurfaceLock.unlock();
return DRAW_ERROR;
}
mInDrawing = true;
mDrawMode = DRAW_GLES_2;
return DRAW_GLES_2;
}
public void endDrawing() {
if (!mInDrawing) {
Log.e(LOG_FILE_NAME, "!! endDrawing without beginDrawing!");
return;
}
if (mDrawSingleFrame)
mAbortDraw = true;
try {
if (!mSurfaceValid) {
Log.e(LOG_FILE_NAME, "!! endDrawing with false mSurfaceValid");
return;
}
} finally {
mInDrawing = false;
if (!mSurfaceLock.isHeldByCurrentThread())
Log.e(LOG_FILE_NAME, "!! endDrawing while mSurfaceLock not held by current thread!");
mSurfaceLock.unlock();
}
}
/* How this works:
* Whenever we want to draw, we want to be sure that we do not lock
* the canvas unless we're sure we can draw. Locking the canvas clears
* the canvas to black in most cases, causing a black flash.
* At the same time, the surface can resize/disappear at any moment
* unless the canvas is locked.
* Draws originate from a different thread so the surface could change
* at any moment while we try to draw until we lock the canvas.
*
* Also, never try to lock the canvas while holding the surface lock
* unless you're in SurfaceChanged, in which case the canvas was already
* locked. Surface lock -> Canvas lock will lead to AB-BA deadlocks.
*/
public void draw2D(Bitmap bitmap, int width, int height) {
// mSurfaceLock ensures that we get mSyncDraw/mSoftwareBitmap/etc.
// set correctly before determining whether we should do a sync draw
Log.e(LOG_FILE_NAME, "!!! draw2d1 !!!");
mSurfaceLock.lock();
try {
if (mSyncDraw) {
if (bitmap != mSoftwareBitmap || width != mWidth || height != mHeight)
return;
mSyncDraw = false;
try {
mSyncDraws.put(bitmap);
} catch (InterruptedException ie) {
Log.e(LOG_FILE_NAME, "!! Threw exception while getting sync draws queue: ", ie);
}
return;
}
} finally {
mSurfaceLock.unlock();
}
draw(getHolder(), bitmap);
}
public void draw2D(ByteBuffer buffer, int stride) {
Log.e(LOG_FILE_NAME, "!!! draw2d2 !!!");
mSurfaceLock.lock();
try {
if (mSyncDraw) {
if (buffer != mSoftwareBuffer || stride != (mWidth * 2))
return;
mSyncDraw = false;
try {
mSyncDraws.put(buffer);
} catch (InterruptedException ie) {
Log.e(LOG_FILE_NAME, "!! Threw exception while getting sync bitmaps queue: ", ie);
}
return;
}
} finally {
mSurfaceLock.unlock();
}
draw(getHolder(), buffer);
}
@Override
public boolean onCheckIsTextEditor () {
return false;
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
outAttrs.actionLabel = null;
mKeyListener = TextKeyListener.getInstance();
if (mIMEState == IME_STATE_PASSWORD)
outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
else if (mIMETypeHint.equalsIgnoreCase("url"))
outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_URI;
else if (mIMETypeHint.equalsIgnoreCase("email"))
outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
else if (mIMETypeHint.equalsIgnoreCase("search"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
else if (mIMETypeHint.equalsIgnoreCase("tel"))
outAttrs.inputType = InputType.TYPE_CLASS_PHONE;
else if (mIMETypeHint.equalsIgnoreCase("number") ||
mIMETypeHint.equalsIgnoreCase("range"))
outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;
else if (mIMETypeHint.equalsIgnoreCase("datetime") ||
mIMETypeHint.equalsIgnoreCase("datetime-local"))
outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
InputType.TYPE_DATETIME_VARIATION_NORMAL;
else if (mIMETypeHint.equalsIgnoreCase("date"))
outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
InputType.TYPE_DATETIME_VARIATION_DATE;
else if (mIMETypeHint.equalsIgnoreCase("time"))
outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
InputType.TYPE_DATETIME_VARIATION_TIME;
if (mIMEActionHint.equalsIgnoreCase("go"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_GO;
else if (mIMEActionHint.equalsIgnoreCase("done"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
else if (mIMEActionHint.equalsIgnoreCase("next"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
else if (mIMEActionHint.equalsIgnoreCase("search"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
else if (mIMEActionHint.equalsIgnoreCase("send"))
outAttrs.imeOptions = EditorInfo.IME_ACTION_SEND;
else if (mIMEActionHint != null && mIMEActionHint.length() != 0)
outAttrs.actionLabel = mIMEActionHint;
if (mIMELandscapeFS == false)
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
inputConnection.reset();
return inputConnection;
}
public void setEditable(String contents)
{
mEditable.removeSpan(inputConnection);
mEditable.replace(0, mEditable.length(), contents);
mEditable.setSpan(inputConnection, 0, contents.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
Selection.setSelection(mEditable, contents.length());
}
public void initEditable(String contents)
{
mEditable = mEditableFactory.newEditable(contents);
mEditable.setSpan(inputConnection, 0, contents.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
Selection.setSelection(mEditable, contents.length());
}
// accelerometer
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
}
public void onSensorChanged(SensorEvent event)
{
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
}
private class GeocoderTask extends AsyncTask<Location, Void, Void> {
protected Void doInBackground(Location... location) {
try {
List<Address> addresses = mGeocoder.getFromLocation(location[0].getLatitude(),
location[0].getLongitude(), 1);
// grab the first address. in the future,
// may want to expose multiple, or filter
// for best.
mLastGeoAddress = addresses.get(0);
GeckoAppShell.sendEventToGecko(new GeckoEvent(location[0], mLastGeoAddress));
} catch (Exception e) {
Log.w(LOG_FILE_NAME, "GeocoderTask "+e);
}
return null;
}
}
// geolocation
public void onLocationChanged(Location location)
{
if (mGeocoder == null)
mGeocoder = new Geocoder(getContext(), Locale.getDefault());
if (mLastGeoAddress == null) {
new GeocoderTask().execute(location);
}
else {
float[] results = new float[1];
Location.distanceBetween(location.getLatitude(),
location.getLongitude(),
mLastGeoAddress.getLatitude(),
mLastGeoAddress.getLongitude(),
results);
// pfm value. don't want to slam the
// geocoder with very similar values, so
// only call after about 100m
if (results[0] > 100)
new GeocoderTask().execute(location);
}
GeckoAppShell.sendEventToGecko(new GeckoEvent(location, mLastGeoAddress));
}
public void onProviderDisabled(String provider)
{
}
public void onProviderEnabled(String provider)
{
}
public void onStatusChanged(String provider, int status, Bundle extras)
{
}
// event stuff
public boolean onTouchEvent(MotionEvent event) {
requestFocus(FOCUS_UP, null);
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
return gestureScanner.onTouchEvent(event);
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (event.isSystem())
return super.onKeyPreIme(keyCode, event);
switch (event.getAction()) {
case KeyEvent.ACTION_DOWN:
return processKeyDown(keyCode, event, true);
case KeyEvent.ACTION_UP:
return processKeyUp(keyCode, event, true);
case KeyEvent.ACTION_MULTIPLE:
return onKeyMultiple(keyCode, event.getRepeatCount(), event);
}
return super.onKeyPreIme(keyCode, event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return processKeyDown(keyCode, event, false);
}
private boolean processKeyDown(int keyCode, KeyEvent event, boolean isPreIme) {
switch (keyCode) {
case KeyEvent.KEYCODE_MENU:
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_SEARCH:
return false;
case KeyEvent.KEYCODE_DEL:
// See comments in GeckoInputConnection.onKeyDel
if (inputConnection != null &&
inputConnection.onKeyDel()) {
return true;
}
break;
case KeyEvent.KEYCODE_ENTER:
if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 &&
mIMEActionHint.equalsIgnoreCase("next"))
event = new KeyEvent(event.getAction(), KeyEvent.KEYCODE_TAB);
break;
default:
break;
}
if (isPreIme && mIMEState != IME_STATE_DISABLED &&
(event.getMetaState() & KeyEvent.META_ALT_ON) == 0)
// Let active IME process pre-IME key events
return false;
// KeyListener returns true if it handled the event for us.
if (mIMEState == IME_STATE_DISABLED ||
keyCode == KeyEvent.KEYCODE_ENTER ||
keyCode == KeyEvent.KEYCODE_DEL ||
(event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
!mKeyListener.onKeyDown(this, mEditable, keyCode, event))
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
return true;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return processKeyUp(keyCode, event, false);
}
private boolean processKeyUp(int keyCode, KeyEvent event, boolean isPreIme) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_SEARCH:
case KeyEvent.KEYCODE_MENU:
return false;
default:
break;
}
if (isPreIme && mIMEState != IME_STATE_DISABLED &&
(event.getMetaState() & KeyEvent.META_ALT_ON) == 0)
// Let active IME process pre-IME key events
return false;
if (mIMEState == IME_STATE_DISABLED ||
keyCode == KeyEvent.KEYCODE_ENTER ||
keyCode == KeyEvent.KEYCODE_DEL ||
(event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
!mKeyListener.onKeyUp(this, mEditable, keyCode, event))
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
return true;
}
@Override
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
return true;
}
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_MENU:
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInputFromWindow(getWindowToken(),
imm.SHOW_FORCED, 0);
return true;
default:
break;
}
return false;
}
// Is this surface valid for drawing into?
boolean mSurfaceValid;
// Are we actively between beginDrawing/endDrawing?
boolean mInDrawing;
// Used to finish the current buffer before changing the surface size
boolean mDrawSingleFrame = false;
boolean mAbortDraw = false;
// Are we waiting for a buffer to draw in surfaceChanged?
boolean mSyncDraw;
// True if gecko requests a buffer
int mDrawMode;
static boolean mShowingLoadScreen = true;
// let's not change stuff around while we're in the middle of
// starting drawing, ending drawing, or changing surface
// characteristics
ReentrantLock mSurfaceLock;
// Surface format, from surfaceChanged. Largely
// useless.
int mFormat;
// the dimensions of the surface
int mWidth;
int mHeight;
// the dimensions of the buffer we're using for drawing,
// that is the software buffer or the EGLSurface
int mBufferWidth;
int mBufferHeight;
// IME stuff
public static final int IME_STATE_DISABLED = 0;
public static final int IME_STATE_ENABLED = 1;
public static final int IME_STATE_PASSWORD = 2;
public static final int IME_STATE_PLUGIN = 3;
GeckoInputConnection inputConnection;
GeckoGestureDetector gestureScanner;
KeyListener mKeyListener;
Editable mEditable;
Editable.Factory mEditableFactory;
int mIMEState;
String mIMETypeHint;
String mIMEActionHint;
boolean mIMELandscapeFS;
// Software rendering
Bitmap mSoftwareBitmap;
ByteBuffer mSoftwareBuffer;
Bitmap mSoftwareBufferCopy;
Bitmap mStartupBitmap;
Geocoder mGeocoder;
Address mLastGeoAddress;
final SynchronousQueue<Object> mSyncDraws = new SynchronousQueue<Object>();
}

View File

@ -61,8 +61,6 @@ JAVAFILES = \
GeckoEventListener.java \
GeckoInputConnection.java \
GeckoPreferences.java \
GeckoSurfaceView.java \
GeckoGestureDetector.java \
GlobalHistory.java \
PromptService.java \
SurfaceInfo.java \
@ -70,6 +68,29 @@ JAVAFILES = \
Tabs.java \
TabsTray.java \
GeckoBatteryManager.java \
gfx/BufferedCairoImage.java \
gfx/CairoImage.java \
gfx/CairoUtils.java \
gfx/FloatPoint.java \
gfx/FloatRect.java \
gfx/GeckoSoftwareLayerClient.java \
gfx/InputConnectionHandler.java \
gfx/IntPoint.java \
gfx/IntRect.java \
gfx/IntSize.java \
gfx/Layer.java \
gfx/LayerClient.java \
gfx/LayerController.java \
gfx/LayerRenderer.java \
gfx/LayerView.java \
gfx/NinePatchTileLayer.java \
gfx/PlaceholderLayerClient.java \
gfx/SingleTileLayer.java \
gfx/TextureReaper.java \
gfx/TextLayer.java \
gfx/TileLayer.java \
ui/PanZoomController.java \
ui/ViewportController.java \
$(NULL)
PROCESSEDJAVAFILES = \
@ -289,6 +310,8 @@ MOZ_ANDROID_DRAWABLES += embedding/android/resources/drawable/address_bar_bg.xml
embedding/android/resources/drawable/tabs_plus.png \
embedding/android/resources/drawable/tabs_menu.png \
embedding/android/resources/drawable/tabs_tray_bg.9.png \
embedding/android/resources/drawable/checkerboard.png \
embedding/android/resources/drawable/shadow.png \
$(NULL)

View File

@ -0,0 +1,73 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.CairoImage;
import org.mozilla.gecko.gfx.CairoUtils;
import android.graphics.Bitmap;
import java.nio.ByteBuffer;
/** A Cairo image that simply saves a buffer of pixel data. */
public class BufferedCairoImage extends CairoImage {
private ByteBuffer mBuffer;
private int mWidth, mHeight, mFormat;
/** Creates a buffered Cairo image from a byte buffer. */
public BufferedCairoImage(ByteBuffer inBuffer, int inWidth, int inHeight, int inFormat) {
mBuffer = inBuffer; mWidth = inWidth; mHeight = inHeight; mFormat = inFormat;
}
/** Creates a buffered Cairo image from an Android bitmap. */
public BufferedCairoImage(Bitmap bitmap) {
mFormat = CairoUtils.bitmapConfigToCairoFormat(bitmap.getConfig());
mWidth = bitmap.getWidth();
mHeight = bitmap.getHeight();
mBuffer = ByteBuffer.allocateDirect(mWidth * mHeight * 4);
bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
}
@Override
public ByteBuffer lockBuffer() { return mBuffer; }
@Override
public int getWidth() { return mWidth; }
@Override
public int getHeight() { return mHeight; }
@Override
public int getFormat() { return mFormat; }
}

View File

@ -0,0 +1,60 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import java.nio.ByteBuffer;
/*
* A bitmap with pixel data in one of the formats that Cairo understands.
*/
public abstract class CairoImage {
public abstract ByteBuffer lockBuffer();
public void unlockBuffer() { /* By default, a no-op. */ }
public abstract int getWidth();
public abstract int getHeight();
public abstract int getFormat();
public static final int FORMAT_INVALID = -1;
public static final int FORMAT_ARGB32 = 0;
public static final int FORMAT_RGB24 = 1;
public static final int FORMAT_A8 = 2;
public static final int FORMAT_A1 = 3;
public static final int FORMAT_RGB16_565 = 4;
}

View File

@ -0,0 +1,120 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.CairoImage;
import android.graphics.Bitmap;
import javax.microedition.khronos.opengles.GL10;
/**
* Utility methods useful when displaying Cairo bitmaps using OpenGL ES.
*/
public class CairoUtils {
private CairoUtils() { /* Don't call me. */ }
public static int cairoFormatToGLInternalFormat(int cairoFormat) {
switch (cairoFormat) {
case CairoImage.FORMAT_ARGB32:
return GL10.GL_RGBA;
case CairoImage.FORMAT_RGB24:
case CairoImage.FORMAT_RGB16_565:
return GL10.GL_RGB;
case CairoImage.FORMAT_A8:
case CairoImage.FORMAT_A1:
throw new RuntimeException("Cairo FORMAT_A1 and FORMAT_A8 unsupported");
default:
throw new RuntimeException("Unknown Cairo format");
}
}
public static int cairoFormatToGLFormat(int cairoFormat) {
switch (cairoFormat) {
case CairoImage.FORMAT_ARGB32:
return GL10.GL_RGBA;
case CairoImage.FORMAT_RGB24:
case CairoImage.FORMAT_RGB16_565:
return GL10.GL_RGB;
case CairoImage.FORMAT_A8:
case CairoImage.FORMAT_A1:
return GL10.GL_ALPHA;
default:
throw new RuntimeException("Unknown Cairo format");
}
}
public static int cairoFormatToGLType(int cairoFormat) {
switch (cairoFormat) {
case CairoImage.FORMAT_ARGB32:
case CairoImage.FORMAT_RGB24:
case CairoImage.FORMAT_A8:
return GL10.GL_UNSIGNED_BYTE;
case CairoImage.FORMAT_A1:
throw new RuntimeException("Cairo FORMAT_A1 unsupported in Android OpenGL");
case CairoImage.FORMAT_RGB16_565:
return GL10.GL_UNSIGNED_SHORT_5_6_5;
default:
throw new RuntimeException("Unknown Cairo format");
}
}
public static int bitsPerPixelForCairoFormat(int cairoFormat) {
switch (cairoFormat) {
case CairoImage.FORMAT_A1: return 1;
case CairoImage.FORMAT_A8: return 8;
case CairoImage.FORMAT_RGB16_565: return 16;
case CairoImage.FORMAT_RGB24: return 24;
case CairoImage.FORMAT_ARGB32: return 32;
default:
throw new RuntimeException("Unknown Cairo format");
}
}
public static int bitmapConfigToCairoFormat(Bitmap.Config config) {
if (config == null)
return CairoImage.FORMAT_ARGB32; /* Droid Pro fix. */
switch (config) {
case ALPHA_8: return CairoImage.FORMAT_A8;
case ARGB_4444: throw new RuntimeException("ARGB_444 unsupported");
case ARGB_8888: return CairoImage.FORMAT_ARGB32;
case RGB_565: return CairoImage.FORMAT_RGB16_565;
default: throw new RuntimeException("Unknown Skia bitmap config");
}
}
}

View File

@ -0,0 +1,71 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.IntPoint;
public class FloatPoint {
public final float x, y;
public FloatPoint(float inX, float inY) {
x = inX; y = inY;
}
public FloatPoint(IntPoint intPoint) {
x = intPoint.x; y = intPoint.y;
}
@Override
public String toString() {
return "(" + x + ", " + y + ")";
}
public FloatPoint add(FloatPoint other) {
return new FloatPoint(x + other.x, y + other.y);
}
public FloatPoint subtract(FloatPoint other) {
return new FloatPoint(x - other.x, y - other.y);
}
public FloatPoint scale(float factor) {
return new FloatPoint(x * factor, y * factor);
}
}

View File

@ -0,0 +1,98 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.FloatPoint;
import org.mozilla.gecko.gfx.IntRect;
public class FloatRect {
public final float x, y, width, height;
public FloatRect(float inX, float inY, float inWidth, float inHeight) {
x = inX; y = inY; width = inWidth; height = inHeight;
}
public FloatRect(IntRect intRect) {
x = intRect.x; y = intRect.y; width = intRect.width; height = intRect.height;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof FloatRect))
return false;
FloatRect otherRect = (FloatRect)other;
return x == otherRect.x && y == otherRect.y &&
width == otherRect.width && height == otherRect.height;
}
public float getRight() { return x + width; }
public float getBottom() { return y + height; }
public FloatPoint getOrigin() { return new FloatPoint(x, y); }
public FloatPoint getCenter() { return new FloatPoint(x + width / 2, y + height / 2); }
/** Returns the intersection of this rectangle with another rectangle. */
public FloatRect intersect(FloatRect other) {
float left = Math.max(x, other.x);
float top = Math.max(y, other.y);
float right = Math.min(getRight(), other.getRight());
float bottom = Math.min(getBottom(), other.getBottom());
return new FloatRect(left, top, Math.max(right - left, 0), Math.max(bottom - top, 0));
}
/** Returns true if and only if the given rectangle is fully enclosed within this one. */
public boolean contains(FloatRect other) {
return x <= other.x && y <= other.y &&
getRight() >= other.getRight() &&
getBottom() >= other.getBottom();
}
/** Contracts a rectangle by the given number of units in each direction, from the center. */
public FloatRect contract(float lessWidth, float lessHeight) {
float halfWidth = width / 2.0f - lessWidth, halfHeight = height / 2.0f - lessHeight;
FloatPoint center = getCenter();
return new FloatRect(center.x - halfWidth, center.y - halfHeight,
halfWidth * 2.0f, halfHeight * 2.0f);
}
/** Scales all four dimensions of this rectangle by the given factor. */
public FloatRect scaleAll(float factor) {
return new FloatRect(x * factor, y * factor, width * factor, height * factor);
}
}

View File

@ -0,0 +1,273 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.CairoImage;
import org.mozilla.gecko.gfx.FloatRect;
import org.mozilla.gecko.gfx.IntRect;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerClient;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.LayerRenderer;
import org.mozilla.gecko.gfx.SingleTileLayer;
import org.mozilla.gecko.ui.ViewportController;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import android.content.Context;
import android.graphics.Point;
import android.util.Log;
import java.nio.ByteBuffer;
import java.util.concurrent.Semaphore;
import java.util.Timer;
import java.util.TimerTask;
/**
* Transfers a software-rendered Gecko to an ImageLayer so that it can be rendered by our
* compositor.
*
* TODO: Throttle down Gecko's priority when we pan and zoom.
*/
public class GeckoSoftwareLayerClient extends LayerClient {
private Context mContext;
private int mWidth, mHeight, mFormat;
private ByteBuffer mBuffer;
private Semaphore mBufferSemaphore;
private SingleTileLayer mTileLayer;
private ViewportController mViewportController;
private FloatRect mGeckoVisibleRect;
/* The viewport rect that Gecko is currently displaying. */
private IntRect mJSPanningToRect;
/* The rect that we just told chrome JavaScript to pan to. */
private boolean mWaitingForJSPanZoom;
/* This will be set to true if we are waiting on the chrome JavaScript to finish panning or
* zooming before we can render. */
private CairoImage mCairoImage;
/* The initial page width and height that we use before a page is loaded. */
private static final int PAGE_WIDTH = 980; /* Matches MobileSafari. */
private static final int PAGE_HEIGHT = 1500;
public GeckoSoftwareLayerClient(Context context) {
mContext = context;
mViewportController = new ViewportController(new IntSize(PAGE_WIDTH, PAGE_HEIGHT),
new FloatRect(0, 0, 1, 1));
mWidth = LayerController.TILE_WIDTH;
mHeight = LayerController.TILE_HEIGHT;
mFormat = CairoImage.FORMAT_RGB16_565;
mBuffer = ByteBuffer.allocateDirect(mWidth * mHeight * 2);
mBufferSemaphore = new Semaphore(1);
mWaitingForJSPanZoom = false;
mCairoImage = new CairoImage() {
@Override
public ByteBuffer lockBuffer() {
try {
mBufferSemaphore.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return mBuffer;
}
@Override
public void unlockBuffer() {
mBufferSemaphore.release();
}
@Override
public int getWidth() { return mWidth; }
@Override
public int getHeight() { return mHeight; }
@Override
public int getFormat() { return mFormat; }
};
mTileLayer = new SingleTileLayer();
}
/** Attaches the root layer to the layer controller so that Gecko appears. */
@Override
public void init() {
getLayerController().setRoot(mTileLayer);
}
public void beginDrawing() {
/* no-op */
}
/*
* TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require
* a little more JNI magic.
*/
public void endDrawing(int x, int y, int width, int height) {
LayerController controller = getLayerController();
//controller.unzoom(); /* FIXME */
controller.notifyViewOfGeometryChange();
mViewportController.setVisibleRect(mGeckoVisibleRect);
if (mGeckoVisibleRect != null) {
FloatRect layerRect = mViewportController.untransformVisibleRect(mGeckoVisibleRect,
getPageSize());
mTileLayer.origin = layerRect.getOrigin();
}
repaint(new IntRect(x, y, width, height));
}
private void repaint(IntRect rect) {
mTileLayer.paintSubimage(mCairoImage, rect);
}
/** Called whenever the chrome JS finishes panning or zooming to some location. */
public void jsPanZoomCompleted(IntRect rect) {
mGeckoVisibleRect = new FloatRect(rect);
if (mWaitingForJSPanZoom)
render();
}
/**
* Acquires a lock on the back buffer and returns it, blocking until it's unlocked. This
* function is for Gecko to use.
*/
public ByteBuffer lockBuffer() {
try {
mBufferSemaphore.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return mBuffer;
}
/**
* Releases the lock on the back buffer. After this call, it is forbidden for Gecko to touch
* the buffer. This function is, again, for Gecko to use.
*/
public void unlockBuffer() {
mBufferSemaphore.release();
}
/** Called whenever the page changes size. */
public void setPageSize(IntSize pageSize) {
mViewportController.setPageSize(pageSize);
getLayerController().setPageSize(pageSize);
}
@Override
public void geometryChanged() {
mViewportController.setVisibleRect(getTransformedVisibleRect());
render();
}
@Override
public IntSize getPageSize() { return mViewportController.getPageSize(); }
@Override
public void render() {
LayerController layerController = getLayerController();
FloatRect visibleRect = layerController.getVisibleRect();
FloatRect tileRect = mViewportController.widenRect(visibleRect);
tileRect = mViewportController.clampRect(tileRect);
IntSize pageSize = layerController.getPageSize();
FloatRect viewportRect = mViewportController.transformVisibleRect(tileRect, pageSize);
/* Prevent null pointer exceptions at the start. */
if (mGeckoVisibleRect == null)
mGeckoVisibleRect = viewportRect;
if (!getLayerController().getRedrawHint())
return;
/* If Gecko's visible rect is the same as our visible rect, then we can actually kick off a
* draw event. */
if (mGeckoVisibleRect.equals(viewportRect)) {
mWaitingForJSPanZoom = false;
mJSPanningToRect = null;
GeckoAppShell.scheduleRedraw();
return;
}
/* Otherwise, we need to get Gecko's visible rect equal to our visible rect before we can
* safely draw. If we're just waiting for chrome JavaScript to catch up, we do nothing.
* This check avoids bombarding the chrome JavaScript with messages. */
IntRect panToRect = new IntRect((int)Math.round(viewportRect.x),
(int)Math.round(viewportRect.y),
LayerController.TILE_WIDTH,
LayerController.TILE_HEIGHT);
if (mWaitingForJSPanZoom && mJSPanningToRect != null &&
mJSPanningToRect.equals(panToRect)) {
return;
}
/* We send Gecko a message telling it to move its visible rect to the appropriate spot and
* set a flag to remind us to try the redraw again. */
GeckoAppShell.sendEventToGecko(new GeckoEvent("PanZoom:PanZoom",
"{\"x\": " + panToRect.x + ", \"y\": " + panToRect.y +
", \"width\": " + panToRect.width + ", \"height\": " + panToRect.height +
", \"zoomFactor\": " + getZoomFactor() + "}"));
mJSPanningToRect = panToRect;
mWaitingForJSPanZoom = true;
}
/* Returns the dimensions of the box in page coordinates that the user is viewing. */
private FloatRect getTransformedVisibleRect() {
LayerController layerController = getLayerController();
return mViewportController.transformVisibleRect(layerController.getVisibleRect(),
layerController.getPageSize());
}
private float getZoomFactor() {
return 1.0f; // FIXME
/*LayerController layerController = getLayerController();
return mViewportController.getZoomFactor(layerController.getVisibleRect(),
layerController.getPageSize(),
layerController.getScreenSize());*/
}
}

View File

@ -0,0 +1,15 @@
package org.mozilla.gecko.gfx;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.KeyEvent;
public interface InputConnectionHandler
{
InputConnection onCreateInputConnection(EditorInfo outAttrs);
boolean onKeyPreIme(int keyCode, KeyEvent event);
boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
}

View File

@ -0,0 +1,60 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
public class IntPoint {
public final int x, y;
public IntPoint(int inX, int inY) { x = inX; y = inY; }
@Override
public String toString() { return "(" + x + ", " + y + ")"; }
/** Returns the result of adding the given point to this point. */
public IntPoint add(IntPoint other) { return new IntPoint(x + other.x, y + other.y); }
/** Returns the result of subtracting the given point from this point. */
public IntPoint subtract(IntPoint other) { return new IntPoint(x - other.x, y - other.y); }
/** Returns the result of multiplying both components by the given scalar. */
public IntPoint scale(float scale) {
return new IntPoint((int)Math.round((float)x * scale), (int)Math.round((float)y * scale));
}
}

View File

@ -0,0 +1,94 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.IntPoint;
import org.json.JSONException;
import org.json.JSONObject;
public class IntRect implements Cloneable {
public final int x, y, width, height;
public IntRect(int inX, int inY, int inWidth, int inHeight) {
x = inX; y = inY; width = inWidth; height = inHeight;
}
public IntRect(JSONObject json) {
try {
x = json.getInt("x");
y = json.getInt("y");
width = json.getInt("width");
height = json.getInt("height");
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@Override
public Object clone() { return new IntRect(x, y, width, height); }
@Override
public boolean equals(Object other) {
if (!(other instanceof IntRect))
return false;
IntRect otherRect = (IntRect)other;
return x == otherRect.x && y == otherRect.y && width == otherRect.width &&
height == otherRect.height;
}
@Override
public String toString() { return "(" + x + "," + y + "," + width + "," + height + ")"; }
public IntPoint getOrigin() { return new IntPoint(x, y); }
public IntPoint getCenter() { return new IntPoint(x + width / 2, y + height / 2); }
public int getRight() { return x + width; }
public int getBottom() { return y + height; }
/** Contracts a rectangle by the given number of units in each direction, from the center. */
public IntRect contract(int lessWidth, int lessHeight) {
float halfWidth = width / 2.0f - lessWidth, halfHeight = height / 2.0f - lessHeight;
IntPoint center = getCenter();
return new IntRect((int)Math.round((float)center.x - halfWidth),
(int)Math.round((float)center.y - halfHeight),
(int)Math.round(halfWidth * 2.0f),
(int)Math.round(halfHeight * 2.0f));
}
}

View File

@ -15,11 +15,11 @@
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Wes Johnston <wjohnston@mozilla.com>
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -35,62 +35,31 @@
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko;
package org.mozilla.gecko.gfx;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.content.Context;
import android.view.View;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
class GeckoGestureDetector implements GestureDetector.OnGestureListener {
private GestureDetector mDetector;
private static final String LOG_FILE_NAME = "GeckoGestureDetector";
public GeckoGestureDetector(Context aContext) {
mDetector = new GestureDetector(aContext, this);
}
public class IntSize {
public final int width, height;
public boolean onTouchEvent(MotionEvent event) {
return mDetector.onTouchEvent(event);
}
public IntSize(int inWidth, int inHeight) { width = inWidth; height = inHeight; }
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return true;
}
@Override
public void onLongPress(MotionEvent motionEvent) {
JSONObject ret = new JSONObject();
public IntSize(JSONObject json) {
try {
ret.put("x", motionEvent.getX());
ret.put("y", motionEvent.getY());
} catch(Exception ex) {
Log.w(LOG_FILE_NAME, "Error building return: " + ex);
width = json.getInt("width");
height = json.getInt("height");
} catch (JSONException e) {
throw new RuntimeException(e);
}
GeckoEvent e = new GeckoEvent("Gesture:LongPress", ret.toString());
GeckoAppShell.sendEventToGecko(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return true;
}
public String toString() { return "(" + width + "," + height + ")"; }
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
public IntSize scale(float factor) {
return new IntSize((int)Math.round(width * factor),
(int)Math.round(height * factor));
}
}

View File

@ -0,0 +1,65 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.FloatPoint;
import javax.microedition.khronos.opengles.GL10;
public abstract class Layer {
public FloatPoint origin;
public Layer() {
origin = new FloatPoint(0.0f, 0.0f);
}
/** Draws the layer. Automatically applies the translation. */
public final void draw(GL10 gl) {
gl.glPushMatrix();
gl.glTranslatef(origin.x, origin.y, 0.0f);
onDraw(gl);
gl.glPopMatrix();
}
/**
* Subclasses implement this method to perform drawing.
*
* Invariant: The current matrix mode must be GL_MODELVIEW both before and after this call.
*/
protected abstract void onDraw(GL10 gl);
}

View File

@ -0,0 +1,64 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.IntRect;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerController;
/**
* A layer client provides tiles and manages other information used by the layer controller.
*/
public abstract class LayerClient {
private LayerController mLayerController;
public abstract void geometryChanged();
public abstract IntSize getPageSize();
/** Called whenever the page changes size. */
public abstract void setPageSize(IntSize pageSize);
public abstract void init();
protected abstract void render();
public LayerController getLayerController() { return mLayerController; }
public void setLayerController(LayerController layerController) {
mLayerController = layerController;
}
}

View File

@ -0,0 +1,288 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.FloatPoint;
import org.mozilla.gecko.gfx.FloatRect;
import org.mozilla.gecko.gfx.IntRect;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.Layer;
import org.mozilla.gecko.gfx.LayerClient;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.ui.PanZoomController;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.view.MotionEvent;
import android.view.GestureDetector;
import android.view.ScaleGestureDetector;
import android.view.View.OnTouchListener;
import java.util.ArrayList;
/**
* The layer controller manages a tile that represents the visible page. It does panning and
* zooming natively by delegating to a panning/zooming controller. Touch events can be dispatched
* to a higher-level view.
*/
public class LayerController {
private Layer mRootLayer; /* The root layer. */
private LayerView mView; /* The main rendering view. */
private Context mContext; /* The current context. */
private FloatRect mVisibleRect; /* The current visible region. */
private IntSize mScreenSize; /* The screen size of the viewport. */
private IntSize mPageSize; /* The current page size. */
private PanZoomController mPanZoomController;
/*
* The panning and zooming controller, which interprets pan and zoom gestures for us and
* updates our visible rect appropriately.
*/
private OnTouchListener mOnTouchListener; /* The touch listener. */
private LayerClient mLayerClient; /* The layer client. */
public static final int TILE_WIDTH = 1024;
public static final int TILE_HEIGHT = 2048;
/* NB: These must be powers of two due to the OpenGL ES 1.x restriction on NPOT textures. */
private static final int DANGER_ZONE_X = 150;
private static final int DANGER_ZONE_Y = 300;
/* If the visible rect is within the danger zone (measured in pixels from each edge of a tile),
* we start aggressively redrawing to minimize checkerboarding. */
public LayerController(Context context, LayerClient layerClient) {
mContext = context;
mVisibleRect = new FloatRect(0.0f, 0.0f, 1.0f, 1.0f);
/* Gets filled in when the surface changes. */
mScreenSize = new IntSize(1, 1);
if (layerClient != null)
setLayerClient(layerClient);
else
mPageSize = new IntSize(LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT);
mPanZoomController = new PanZoomController(this);
mView = new LayerView(context, this);
}
public void setRoot(Layer layer) { mRootLayer = layer; }
public void setLayerClient(LayerClient layerClient) {
mLayerClient = layerClient;
mPageSize = layerClient.getPageSize();
layerClient.setLayerController(this);
}
public Layer getRoot() { return mRootLayer; }
public LayerView getView() { return mView; }
public Context getContext() { return mContext; }
public FloatRect getVisibleRect() { return mVisibleRect; }
public IntSize getScreenSize() { return mScreenSize; }
public IntSize getPageSize() { return mPageSize; }
public Bitmap getCheckerboardPattern() { return getDrawable("checkerboard"); }
public Bitmap getShadowPattern() { return getDrawable("shadow"); }
public GestureDetector.OnGestureListener getGestureListener() { return mPanZoomController; }
public ScaleGestureDetector.OnScaleGestureListener getScaleGestureListener() { return mPanZoomController; }
private Bitmap getDrawable(String name) {
Resources resources = mContext.getResources();
int resourceID = resources.getIdentifier(name, "drawable", mContext.getPackageName());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
return BitmapFactory.decodeResource(mContext.getResources(), resourceID, options);
}
/*
* Note that the zoom factor of the layer controller differs from the zoom factor of the layer
* client (i.e. the page).
*/
public float getZoomFactor() { return (float)mScreenSize.width / mVisibleRect.width; }
/**
* The view calls this to indicate that the screen changed size.
*
* TODO: Refactor this to use an interface. Expose that interface only to the view and not
* to the layer client. That way, the layer client won't be tempted to call this, which might
* result in an infinite loop.
*/
public void setScreenSize(int width, int height) {
float zoomFactor = getZoomFactor(); /* Must come first. */
mScreenSize = new IntSize(width, height);
setVisibleRect(mVisibleRect.x, mVisibleRect.y, width / zoomFactor, height / zoomFactor);
notifyLayerClientOfGeometryChange();
}
public void setNeedsDisplay() {
// TODO
}
public void scrollTo(float x, float y) {
setVisibleRect(x, y, mVisibleRect.width, mVisibleRect.height);
}
public void setVisibleRect(float x, float y, float width, float height) {
mVisibleRect = new FloatRect(x, y, width, height);
setNeedsDisplay();
}
/**
* Sets the zoom factor to 1, adjusting the visible rect accordingly. The Gecko layer client
* calls this function after a zoom has completed and Gecko is done rendering the new visible
* region.
*/
public void unzoom() {
float zoomFactor = getZoomFactor();
mVisibleRect = new FloatRect(Math.round(mVisibleRect.x * zoomFactor),
Math.round(mVisibleRect.y * zoomFactor),
mScreenSize.width,
mScreenSize.height);
mPageSize = mPageSize.scale(zoomFactor);
setNeedsDisplay();
}
public void setPageSize(IntSize size) {
mPageSize = size.scale(getZoomFactor());
mView.notifyRendererOfPageSizeChange();
}
public boolean post(Runnable action) { return mView.post(action); }
public void setOnTouchListener(OnTouchListener onTouchListener) {
mOnTouchListener = onTouchListener;
}
/**
* The view as well as the controller itself use this method to notify the layer client that
* the geometry changed.
*/
public void notifyLayerClientOfGeometryChange() {
if (mLayerClient != null)
mLayerClient.geometryChanged();
}
// Informs the view and the panning and zooming controller that the geometry changed.
public void notifyViewOfGeometryChange() {
mView.geometryChanged();
mPanZoomController.geometryChanged();
}
/**
* Returns true if this controller is fine with performing a redraw operation or false if it
* would prefer that the action didn't take place.
*/
public boolean getRedrawHint() {
return aboutToCheckerboard();
}
private FloatRect getTileRect() {
return new FloatRect(mRootLayer.origin.x, mRootLayer.origin.y, TILE_WIDTH, TILE_HEIGHT);
}
// Returns true if a checkerboard is about to be visible.
private boolean aboutToCheckerboard() {
IntRect pageRect = new IntRect(0, 0, mPageSize.width, mPageSize.height);
IntRect adjustedPageRect = pageRect.contract(DANGER_ZONE_X, DANGER_ZONE_Y);
FloatRect visiblePageRect = mVisibleRect.intersect(new FloatRect(adjustedPageRect));
FloatRect adjustedTileRect = getTileRect().contract(DANGER_ZONE_X, DANGER_ZONE_Y);
return !adjustedTileRect.contains(visiblePageRect);
}
/** Returns the given rect, clamped to the boundaries of a tile. */
public FloatRect clampRect(FloatRect rect) {
float x = clamp(0, rect.x, mPageSize.width - LayerController.TILE_WIDTH);
float y = clamp(0, rect.y, mPageSize.height - LayerController.TILE_HEIGHT);
return new FloatRect(x, y, rect.width, rect.height);
}
private float clamp(float min, float value, float max) {
if (max < min)
return min;
return (value < min) ? min : (value > max) ? max : value;
}
// Returns the coordinates of a tile, scaled by the given factor, centered on the given rect.
private static FloatRect widenRect(FloatRect rect, float scaleFactor) {
FloatPoint center = rect.getCenter();
float halfTileWidth = TILE_WIDTH * scaleFactor / 2.0f;
float halfTileHeight = TILE_HEIGHT * scaleFactor / 2.0f;
return new FloatRect(center.x - halfTileWidth, center.y - halfTileHeight,
halfTileWidth, halfTileHeight);
}
/** Returns the coordinates of a tile centered on the given rect. */
public static FloatRect widenRect(FloatRect rect) {
return widenRect(rect, 1.0f);
}
/**
* Converts a point from layer view coordinates to layer coordinates. In other words, given a
* point measured in pixels from the top left corner of the layer view, returns the point in
* pixels measured from the top left corner of the root layer, in the coordinate system of the
* layer itself. This method is used by the viewport controller as part of the process of
* translating touch events to Gecko's coordinate system.
*/
public FloatPoint convertViewPointToLayerPoint(FloatPoint viewPoint) {
if (mRootLayer == null)
return null;
// Undo the transforms.
FloatPoint scaledPoint = viewPoint.scale(1.0f / getZoomFactor());
return mVisibleRect.getOrigin().add(scaledPoint).subtract(mRootLayer.origin);
}
/*
* Gesture detection. This is handled only at a high level in this class; we dispatch to the
* pan/zoom controller to do the dirty work.
*/
public boolean onTouchEvent(MotionEvent event) {
boolean result = mPanZoomController.onTouchEvent(event);
if (mOnTouchListener != null)
result = mOnTouchListener.onTouch(mView, event) || result;
return result;
}
}

View File

@ -0,0 +1,202 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.BufferedCairoImage;
import org.mozilla.gecko.gfx.FloatRect;
import org.mozilla.gecko.gfx.IntRect;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.NinePatchTileLayer;
import org.mozilla.gecko.gfx.SingleTileLayer;
import org.mozilla.gecko.gfx.TextureReaper;
import org.mozilla.gecko.gfx.TextLayer;
import org.mozilla.gecko.gfx.TileLayer;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import java.nio.ByteBuffer;
/**
* The layer renderer implements the rendering logic for a layer view.
*/
public class LayerRenderer implements GLSurfaceView.Renderer {
private static final float BACKGROUND_COLOR_R = 0.81f;
private static final float BACKGROUND_COLOR_G = 0.81f;
private static final float BACKGROUND_COLOR_B = 0.81f;
private LayerView mView;
private SingleTileLayer mCheckerboardLayer;
private NinePatchTileLayer mShadowLayer;
private TextLayer mFPSLayer;
// FPS display
private long mFrameCountTimestamp;
private int mFrameCount; // number of frames since last timestamp
public LayerRenderer(LayerView view) {
mView = view;
/* FIXME: Layers should not be directly connected to the layer controller. */
LayerController controller = view.getController();
mCheckerboardLayer = new SingleTileLayer(true);
mCheckerboardLayer.paintImage(new BufferedCairoImage(controller.getCheckerboardPattern()));
mShadowLayer = new NinePatchTileLayer(controller);
mShadowLayer.paintImage(new BufferedCairoImage(controller.getShadowPattern()));
mFPSLayer = new TextLayer(new IntSize(64, 32));
mFPSLayer.setText("-- FPS");
mFrameCountTimestamp = System.currentTimeMillis();
mFrameCount = 0;
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepthf(1.0f); /* FIXME: Is this needed? */
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
gl.glShadeModel(GL10.GL_SMOOTH); /* FIXME: Is this needed? */
gl.glDisable(GL10.GL_DITHER);
gl.glEnable(GL10.GL_TEXTURE_2D);
}
public void onDrawFrame(GL10 gl) {
checkFPS();
TextureReaper.get().reap(gl);
LayerController controller = mView.getController();
/* Draw the background. */
gl.glClearColor(BACKGROUND_COLOR_R, BACKGROUND_COLOR_G, BACKGROUND_COLOR_B, 1.0f);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
/* Draw the drop shadow. */
setupPageTransform(gl);
mShadowLayer.draw(gl);
/* Draw the checkerboard. */
IntRect pageRect = clampToScreen(getPageRect());
IntSize screenSize = controller.getScreenSize();
gl.glEnable(GL10.GL_SCISSOR_TEST);
gl.glScissor(pageRect.x, screenSize.height - (pageRect.y + pageRect.height),
pageRect.width, pageRect.height);
gl.glLoadIdentity();
mCheckerboardLayer.draw(gl);
/* Draw the layer the client added to us. */
setupPageTransform(gl);
Layer rootLayer = controller.getRoot();
if (rootLayer != null)
rootLayer.draw(gl);
gl.glDisable(GL10.GL_SCISSOR_TEST);
/* Draw the FPS. */
gl.glLoadIdentity();
gl.glEnable(GL10.GL_BLEND);
mFPSLayer.draw(gl);
gl.glDisable(GL10.GL_BLEND);
}
public void pageSizeChanged() {
mShadowLayer.recreateVertexBuffers();
}
private void setupPageTransform(GL10 gl) {
LayerController controller = mView.getController();
FloatRect visibleRect = controller.getVisibleRect();
float zoomFactor = controller.getZoomFactor();
gl.glLoadIdentity();
gl.glScalef(zoomFactor, zoomFactor, 1.0f);
gl.glTranslatef(-visibleRect.x, -visibleRect.y, 0.0f);
}
private IntRect getPageRect() {
LayerController controller = mView.getController();
float zoomFactor = controller.getZoomFactor();
FloatRect visibleRect = controller.getVisibleRect();
IntSize pageSize = controller.getPageSize();
return new IntRect((int)Math.round(-zoomFactor * visibleRect.x),
(int)Math.round(-zoomFactor * visibleRect.y),
(int)Math.round(zoomFactor * pageSize.width),
(int)Math.round(zoomFactor * pageSize.height));
}
private IntRect clampToScreen(IntRect rect) {
LayerController controller = mView.getController();
IntSize screenSize = controller.getScreenSize();
int left = Math.max(0, rect.x);
int top = Math.max(0, rect.y);
int right = Math.min(screenSize.width, rect.getRight());
int bottom = Math.min(screenSize.height, rect.getBottom());
return new IntRect(left, top, right - left, bottom - top);
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrthof(0.0f, (float)width, (float)height, 0.0f, -10.0f, 10.0f);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
mView.setScreenSize(width, height);
/* TODO: Throw away tile images? */
}
private void checkFPS() {
if (System.currentTimeMillis() >= mFrameCountTimestamp + 1000) {
mFrameCountTimestamp = System.currentTimeMillis();
mFPSLayer.setText(mFrameCount + " FPS");
mFrameCount = 0;
} else {
mFrameCount++;
}
}
}

View File

@ -0,0 +1,148 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.InputConnectionHandler;
import org.mozilla.gecko.gfx.LayerController;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.ScaleGestureDetector;
/**
* A view rendered by the layer compositor.
*
* This view delegates to LayerRenderer to actually do the drawing. Its role is largely that of a
* mediator between the LayerRenderer and the LayerController.
*/
public class LayerView extends GLSurfaceView {
private Context mContext;
private LayerController mController;
private InputConnectionHandler mInputConnectionHandler;
private LayerRenderer mRenderer;
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
public LayerView(Context context, LayerController controller) {
super(context);
mContext = context;
mController = controller;
mRenderer = new LayerRenderer(this);
setRenderer(mRenderer);
mGestureDetector = new GestureDetector(context, controller.getGestureListener());
mScaleGestureDetector = new ScaleGestureDetector(context, controller.getScaleGestureListener());
mInputConnectionHandler = null;
setFocusable(true);
setFocusableInTouchMode(true);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector.onTouchEvent(event))
return true;
mScaleGestureDetector.onTouchEvent(event);
if (mScaleGestureDetector.isInProgress())
return true;
return mController.onTouchEvent(event);
}
public LayerController getController() { return mController; }
public void geometryChanged() { /* TODO: Schedule a redraw. */ }
public void notifyRendererOfPageSizeChange() {
mRenderer.pageSizeChanged();
}
/** The LayerRenderer calls this to indicate that the window has changed size. */
public void setScreenSize(int width, int height) {
mController.setScreenSize(width, height);
}
public void setInputConnectionHandler(InputConnectionHandler handler) {
mInputConnectionHandler = handler;
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
if (mInputConnectionHandler != null)
return mInputConnectionHandler.onCreateInputConnection(outAttrs);
return null;
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (mInputConnectionHandler != null)
return mInputConnectionHandler.onKeyPreIme(keyCode, event);
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (mInputConnectionHandler != null)
return mInputConnectionHandler.onKeyDown(keyCode, event);
return false;
}
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (mInputConnectionHandler != null)
return mInputConnectionHandler.onKeyLongPress(keyCode, event);
return false;
}
@Override
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
if (mInputConnectionHandler != null)
return mInputConnectionHandler.onKeyMultiple(keyCode, repeatCount, event);
return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (mInputConnectionHandler != null)
return mInputConnectionHandler.onKeyUp(keyCode, event);
return false;
}
}

View File

@ -0,0 +1,176 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.TileLayer;
import javax.microedition.khronos.opengles.GL10;
import java.nio.FloatBuffer;
/**
* Encapsulates the logic needed to draw a nine-patch bitmap using OpenGL ES.
*
* For more information on nine-patch bitmaps, see the following document:
* http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch
*/
public class NinePatchTileLayer extends TileLayer {
private FloatBuffer mSideTexCoordBuffer, mSideVertexBuffer;
private FloatBuffer mTopTexCoordBuffer, mTopVertexBuffer;
private LayerController mLayerController;
private static final int PATCH_SIZE = 16;
private static final int TEXTURE_SIZE = 48;
/*
* We divide the nine-patch bitmap up into the "sides" and the "tops":
*
* Top
* |
* v
* +---+---+---+
* | | | |
* | +---+ |
* | |XXX| | <-- Side
* | +---+ |
* | | | |
* +---+---+---+
*/
private static final float[] SIDE_TEX_COORDS = {
0.0f, 0.0f,
0.25f, 0.0f,
0.0f, 0.25f,
0.25f, 0.25f,
0.0f, 0.50f,
0.25f, 0.50f,
0.0f, 0.75f,
0.25f, 0.75f,
};
private static final float[] TOP_TEX_COORDS = {
0.25f, 0.0f,
0.50f, 0.0f,
0.25f, 0.25f,
0.50f, 0.25f,
};
public NinePatchTileLayer(LayerController layerController) {
super(false);
mLayerController = layerController;
mSideTexCoordBuffer = createBuffer(SIDE_TEX_COORDS);
mTopTexCoordBuffer = createBuffer(TOP_TEX_COORDS);
recreateVertexBuffers();
}
public void recreateVertexBuffers() {
IntSize pageSize = mLayerController.getPageSize();
float[] sideVertices = {
-PATCH_SIZE, -PATCH_SIZE, 0.0f,
0.0f, -PATCH_SIZE, 0.0f,
-PATCH_SIZE, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f,
-PATCH_SIZE, pageSize.height, 0.0f,
0.0f, pageSize.height, 0.0f,
-PATCH_SIZE, PATCH_SIZE + pageSize.height, 0.0f,
0.0f, PATCH_SIZE + pageSize.height, 0.0f
};
float[] topVertices = {
0.0f, -PATCH_SIZE, 0.0f,
pageSize.width, -PATCH_SIZE, 0.0f,
0.0f, 0.0f, 0.0f,
pageSize.width, 0.0f, 0.0f
};
mSideVertexBuffer = createBuffer(sideVertices);
mTopVertexBuffer = createBuffer(topVertices);
}
@Override
protected void onTileDraw(GL10 gl) {
IntSize pageSize = mLayerController.getPageSize();
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glEnable(GL10.GL_BLEND);
gl.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
/* Left side */
drawTriangles(gl, mSideVertexBuffer, mSideTexCoordBuffer, 8);
/* Top */
drawTriangles(gl, mTopVertexBuffer, mTopTexCoordBuffer, 4);
/* Right side */
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glPushMatrix();
gl.glTranslatef(pageSize.width + PATCH_SIZE, 0.0f, 0.0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glPushMatrix();
gl.glTranslatef(0.50f, 0.0f, 0.0f);
drawTriangles(gl, mSideVertexBuffer, mSideTexCoordBuffer, 8);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glPopMatrix();
gl.glMatrixMode(GL10.GL_MODELVIEW); /* Not strictly necessary, but here for clarity. */
gl.glPopMatrix();
/* Bottom */
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glPushMatrix();
gl.glTranslatef(0.0f, pageSize.height + PATCH_SIZE, 0.0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glPushMatrix();
gl.glTranslatef(0.0f, 0.50f, 0.0f);
drawTriangles(gl, mTopVertexBuffer, mTopTexCoordBuffer, 4);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glPopMatrix();
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glPopMatrix();
gl.glDisable(GL10.GL_BLEND);
}
}

View File

@ -0,0 +1,102 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.BufferedCairoImage;
import org.mozilla.gecko.gfx.CairoUtils;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerClient;
import org.mozilla.gecko.gfx.SingleTileLayer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.nio.ByteBuffer;
/**
* A stand-in for Gecko that renders cached content of the previous page. We use this until Gecko
* is up, then we hand off control to it.
*/
public class PlaceholderLayerClient extends LayerClient {
private Context mContext;
private IntSize mPageSize;
private int mWidth, mHeight, mFormat;
private ByteBuffer mBuffer;
private PlaceholderLayerClient(Context context, Bitmap bitmap) {
mContext = context;
mPageSize = new IntSize(995, 1250); /* TODO */
mWidth = bitmap.getWidth();
mHeight = bitmap.getHeight();
mFormat = CairoUtils.bitmapConfigToCairoFormat(bitmap.getConfig());
mBuffer = ByteBuffer.allocateDirect(mWidth * mHeight * 4);
bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
}
public static PlaceholderLayerClient createInstance(Context context) {
File path = new File(Environment.getExternalStorageDirectory(), "lastScreen.png");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
Bitmap bitmap = BitmapFactory.decodeFile("" + path, options);
if (bitmap == null)
return null;
return new PlaceholderLayerClient(context, bitmap);
}
public void init() {
SingleTileLayer tileLayer = new SingleTileLayer();
getLayerController().setRoot(tileLayer);
tileLayer.paintImage(new BufferedCairoImage(mBuffer, mWidth, mHeight, mFormat));
}
@Override
public void geometryChanged() { /* no-op */ }
@Override
public IntSize getPageSize() { return mPageSize; }
@Override
public void render() { /* no-op */ }
/** Called whenever the page changes size. */
@Override
public void setPageSize(IntSize pageSize) { mPageSize = pageSize; }
}

View File

@ -0,0 +1,107 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.CairoImage;
import org.mozilla.gecko.gfx.CairoUtils;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.TileLayer;
import android.util.Log;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
/**
* Encapsulates the logic needed to draw a single textured tile.
*/
public class SingleTileLayer extends TileLayer {
private FloatBuffer mTexCoordBuffer, mVertexBuffer;
private static final float[] VERTICES = {
0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f
};
private static final float[] TEX_COORDS = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
};
public SingleTileLayer() { this(false); }
public SingleTileLayer(boolean repeat) {
super(repeat);
mVertexBuffer = createBuffer(VERTICES);
mTexCoordBuffer = createBuffer(TEX_COORDS);
}
@Override
protected void onTileDraw(GL10 gl) {
IntSize size = getSize();
if (repeats()) {
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glPushMatrix();
gl.glScalef(LayerController.TILE_WIDTH / size.width,
LayerController.TILE_HEIGHT / size.height,
1.0f);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glScalef(LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT, 1.0f);
} else {
gl.glScalef(size.width, size.height, 1.0f);
}
gl.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
drawTriangles(gl, mVertexBuffer, mTexCoordBuffer, 4);
if (repeats()) {
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glPopMatrix();
gl.glMatrixMode(GL10.GL_MODELVIEW);
}
}
}

View File

@ -0,0 +1,99 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.BufferedCairoImage;
import org.mozilla.gecko.gfx.CairoImage;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.SingleTileLayer;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.Log;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
/**
* Draws text on a layer. This is used for the frame rate meter.
*/
public class TextLayer extends SingleTileLayer {
private ByteBuffer mBuffer;
private BufferedCairoImage mImage;
private IntSize mSize;
private String mText;
public TextLayer(IntSize size) {
super(false);
mBuffer = ByteBuffer.allocateDirect(size.width * size.height * 4);
mSize = size;
mImage = new BufferedCairoImage(mBuffer, size.width, size.height,
CairoImage.FORMAT_ARGB32);
mText = "";
}
public void setText(String text) {
mText = text;
renderText();
paintImage(mImage);
}
private void renderText() {
Bitmap bitmap = Bitmap.createBitmap(mSize.width, mSize.height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setColor(Color.WHITE);
textPaint.setFakeBoldText(true);
textPaint.setTextSize(18.0f);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
float width = textPaint.measureText(mText) + 18.0f;
Paint backgroundPaint = new Paint();
backgroundPaint.setColor(Color.argb(127, 0, 0, 0));
canvas.drawRect(0.0f, 0.0f, width, 18.0f + 6.0f, backgroundPaint);
canvas.drawText(mText, 6.0f, 18.0f, textPaint);
bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
}
}

View File

@ -0,0 +1,71 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import javax.microedition.khronos.opengles.GL10;
import java.util.ArrayList;
/** Manages a list of dead tiles, so we don't leak resources. */
public class TextureReaper {
private static TextureReaper sSharedInstance;
private ArrayList<Integer> mDeadTextureIDs;
private TextureReaper() { mDeadTextureIDs = new ArrayList<Integer>(); }
public static TextureReaper get() {
if (sSharedInstance == null)
sSharedInstance = new TextureReaper();
return sSharedInstance;
}
public void add(int[] textureIDs) {
for (int textureID : textureIDs)
mDeadTextureIDs.add(textureID);
}
public void reap(GL10 gl) {
int[] deadTextureIDs = new int[mDeadTextureIDs.size()];
for (int i = 0; i < deadTextureIDs.length; i++)
deadTextureIDs[i] = mDeadTextureIDs.get(i);
mDeadTextureIDs.clear();
gl.glDeleteTextures(deadTextureIDs.length, deadTextureIDs, 0);
}
}

View File

@ -0,0 +1,189 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.CairoImage;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.Layer;
import org.mozilla.gecko.gfx.TextureReaper;
import android.util.Log;
import javax.microedition.khronos.opengles.GL10;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* Base class for tile layers, which encapsulate the logic needed to draw textured tiles in OpenGL
* ES.
*/
public abstract class TileLayer extends Layer {
private CairoImage mImage;
private boolean mRepeat;
private IntSize mSize;
private int[] mTextureIDs;
private IntRect mTextureUploadRect;
/* The rect that needs to be uploaded to the texture. */
public TileLayer(boolean repeat) {
super();
mRepeat = repeat;
mTextureUploadRect = null;
}
public IntSize getSize() { return mSize; }
protected boolean repeats() { return mRepeat; }
protected int getTextureID() { return mTextureIDs[0]; }
@Override
protected void finalize() throws Throwable {
if (mTextureIDs != null)
TextureReaper.get().add(mTextureIDs);
}
/**
* Subclasses implement this method to perform tile drawing.
*
* Invariant: The current matrix mode must be GL_MODELVIEW both before and after this call.
*/
protected abstract void onTileDraw(GL10 gl);
@Override
protected void onDraw(GL10 gl) {
if (mImage == null)
return;
if (mTextureUploadRect != null)
uploadTexture(gl);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glPushMatrix();
onTileDraw(gl);
gl.glPopMatrix();
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
public void paintSubimage(CairoImage image, IntRect rect) {
mImage = image;
mTextureUploadRect = rect;
/*
* Assert that the image has a power-of-two size. OpenGL ES < 2.0 doesn't support NPOT
* textures and OpenGL ES doesn't seem to let us efficiently slice up a NPOT bitmap.
*/
int width = mImage.getWidth(), height = mImage.getHeight();
assert (width & (width - 1)) == 0;
assert (height & (height - 1)) == 0;
}
public void paintImage(CairoImage image) {
paintSubimage(image, new IntRect(0, 0, image.getWidth(), image.getHeight()));
}
private void uploadTexture(GL10 gl) {
boolean newTexture = mTextureIDs == null;
if (newTexture) {
mTextureIDs = new int[1];
gl.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
}
int width = mImage.getWidth(), height = mImage.getHeight();
mSize = new IntSize(width, height);
int cairoFormat = mImage.getFormat();
int internalFormat = CairoUtils.cairoFormatToGLInternalFormat(cairoFormat);
int format = CairoUtils.cairoFormatToGLFormat(cairoFormat);
int type = CairoUtils.cairoFormatToGLType(cairoFormat);
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
int repeatMode = mRepeat ? GL10.GL_REPEAT : GL10.GL_CLAMP_TO_EDGE;
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, repeatMode);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, repeatMode);
ByteBuffer buffer = mImage.lockBuffer();
try {
if (newTexture) {
/* The texture is new; we have to upload the whole image. */
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, internalFormat, mSize.width, mSize.height, 0,
format, type, buffer);
} else {
/*
* The texture is already existing, so upload only the changed rect. We have to
* widen to the full width of the texture because we can't count on the device
* having support for GL_EXT_unpack_subimage, and going line-by-line is too slow.
*/
Buffer viewBuffer = buffer.slice();
int bpp = CairoUtils.bitsPerPixelForCairoFormat(cairoFormat) / 8;
viewBuffer.position(mTextureUploadRect.y * width * bpp);
gl.glTexSubImage2D(gl.GL_TEXTURE_2D,
0, 0, mTextureUploadRect.y, width, mTextureUploadRect.height,
format, type, viewBuffer);
}
} finally {
mImage.unlockBuffer();
}
mTextureUploadRect = null;
}
protected static FloatBuffer createBuffer(float[] values) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(values.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();
floatBuffer.put(values);
floatBuffer.position(0);
return floatBuffer;
}
protected static void drawTriangles(GL10 gl, FloatBuffer vertexBuffer,
FloatBuffer texCoordBuffer, int count) {
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texCoordBuffer);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, count);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

View File

@ -0,0 +1,577 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.ui;
import org.json.JSONObject;
import org.mozilla.gecko.gfx.FloatPoint;
import org.mozilla.gecko.gfx.FloatRect;
import org.mozilla.gecko.gfx.IntPoint;
import org.mozilla.gecko.gfx.IntRect;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import java.util.Timer;
import java.util.TimerTask;
/*
* Handles the kinetic scrolling and zooming physics for a layer controller.
*
* Many ideas are from Joe Hewitt's Scrollability:
* https://github.com/joehewitt/scrollability/
*/
public class PanZoomController
extends GestureDetector.SimpleOnGestureListener
implements ScaleGestureDetector.OnScaleGestureListener
{
private static final String LOG_NAME = "PanZoomController";
private LayerController mController;
private static final float FRICTION = 0.97f;
// Animation stops if the velocity is below this value.
private static final float STOPPED_THRESHOLD = 4.0f;
// The percentage of the surface which can be overscrolled before it must snap back.
private static final float SNAP_LIMIT = 0.75f;
// The rate of deceleration when the surface has overscrolled.
private static final float OVERSCROLL_DECEL_RATE = 0.04f;
// The duration of animation when bouncing back.
private static final int SNAP_TIME = 150;
// The number of subdivisions we should consider when plotting the ease-out transition. Higher
// values make the animation more accurate, but slower to plot.
private static final int SUBDIVISION_COUNT = 1000;
private long mLastTimestamp;
private Timer mFlingTimer;
private Axis mX, mY;
/* The span at the first zoom event (in unzoomed page coordinates). */
private float mInitialZoomSpan;
/* The zoom focus at the first zoom event (in unzoomed page coordinates). */
private FloatPoint mInitialZoomFocus;
private enum PanZoomState {
NOTHING, /* no touch-start events received */
FLING, /* all touches removed, but we're still scrolling page */
TOUCHING, /* one touch-start event received */
PANNING, /* touch-start followed by move */
PANNING_HOLD, /* in panning, but haven't moved.
* similar to TOUCHING but after starting a pan */
PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
}
private PanZoomState mState;
public PanZoomController(LayerController controller) {
mController = controller;
mX = new Axis(); mY = new Axis();
mState = PanZoomState.NOTHING;
populatePositionAndLength();
}
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: return onTouchStart(event);
case MotionEvent.ACTION_MOVE: return onTouchMove(event);
case MotionEvent.ACTION_UP: return onTouchEnd(event);
case MotionEvent.ACTION_CANCEL: return onTouchCancel(event);
default: return false;
}
}
public void geometryChanged() {
populatePositionAndLength();
}
/*
* Panning/scrolling
*/
private boolean onTouchStart(MotionEvent event) {
switch (mState) {
case FLING:
if (mFlingTimer != null) {
mFlingTimer.cancel();
mFlingTimer = null;
}
// fall through
case NOTHING:
mState = PanZoomState.TOUCHING;
mX.touchPos = event.getX(0);
mY.touchPos = event.getY(0);
return false;
case TOUCHING:
case PANNING:
case PANNING_HOLD:
case PINCHING:
mState = PanZoomState.PINCHING;
return false;
}
Log.e(LOG_NAME, "Unhandled case " + mState + " in onTouchStart");
return false;
}
private boolean onTouchMove(MotionEvent event) {
switch (mState) {
case NOTHING:
case FLING:
// should never happen
Log.e(LOG_NAME, "Received impossible touch move while in " + mState);
return false;
case TOUCHING:
mLastTimestamp = System.currentTimeMillis();
// fall through
case PANNING_HOLD:
mState = PanZoomState.PANNING;
// fall through
case PANNING:
track(event, System.currentTimeMillis());
return true;
case PINCHING:
// scale gesture listener will handle this
return false;
}
Log.e(LOG_NAME, "Unhandled case " + mState + " in onTouchMove");
return false;
}
private boolean onTouchEnd(MotionEvent event) {
switch (mState) {
case NOTHING:
case FLING:
// should never happen
Log.e(LOG_NAME, "Received impossible touch end while in " + mState);
return false;
case TOUCHING:
mState = PanZoomState.NOTHING;
// TODO: send click to gecko
return false;
case PANNING:
case PANNING_HOLD:
mState = PanZoomState.FLING;
fling(System.currentTimeMillis());
return true;
case PINCHING:
int points = event.getPointerCount();
if (points == 1) {
// last touch up
mState = PanZoomState.NOTHING;
} else if (points == 2) {
int pointRemovedIndex = event.getActionIndex();
int pointRemainingIndex = 1 - pointRemovedIndex; // kind of a hack
mState = PanZoomState.TOUCHING;
mX.touchPos = event.getX(pointRemainingIndex);
mY.touchPos = event.getY(pointRemainingIndex);
} else {
// still pinching, do nothing
}
return true;
}
Log.e(LOG_NAME, "Unhandled case " + mState + " in onTouchEnd");
return false;
}
private boolean onTouchCancel(MotionEvent event) {
mState = PanZoomState.NOTHING;
return false;
}
private void track(MotionEvent event, long timestamp) {
long timeStep = timestamp - mLastTimestamp;
mLastTimestamp = timestamp;
float zoomFactor = mController.getZoomFactor();
mX.velocity = (mX.touchPos - event.getX(0)) / zoomFactor;
mY.velocity = (mY.touchPos - event.getY(0)) / zoomFactor;
mX.touchPos = event.getX(0); mY.touchPos = event.getY(0);
float absVelocity = (float)Math.sqrt(mX.velocity * mX.velocity +
mY.velocity * mY.velocity);
if (absVelocity < STOPPED_THRESHOLD)
mState = PanZoomState.PANNING_HOLD;
mX.applyEdgeResistance(); mX.displace();
mY.applyEdgeResistance(); mY.displace();
updatePosition();
}
private void fling(long timestamp) {
long timeStep = timestamp - mLastTimestamp;
mLastTimestamp = timestamp;
if (mState != PanZoomState.FLING)
mX.velocity = mY.velocity = 0.0f;
mX.displace(); mY.displace();
if (mFlingTimer != null)
mFlingTimer.cancel();
mX.startFling(); mY.startFling();
mFlingTimer = new Timer();
mFlingTimer.scheduleAtFixedRate(new TimerTask() {
public void run() { mController.post(new FlingRunnable()); }
}, 0, 1000L/60L);
}
private void updatePosition() {
mController.scrollTo(mX.viewportPos, mY.viewportPos);
mController.notifyLayerClientOfGeometryChange();
}
// Populates the viewport info and length in the axes.
private void populatePositionAndLength() {
IntSize pageSize = mController.getPageSize();
FloatRect visibleRect = mController.getVisibleRect();
IntSize screenSize = mController.getScreenSize();
mX.setPageLength(pageSize.width);
mX.viewportPos = visibleRect.x;
mX.setViewportLength(visibleRect.width);
mY.setPageLength(pageSize.height);
mY.viewportPos = visibleRect.y;
mY.setViewportLength(visibleRect.height);
}
// The callback that performs the fling animation.
private class FlingRunnable implements Runnable {
public void run() {
populatePositionAndLength();
mX.advanceFling(); mY.advanceFling();
// If both X and Y axes are overscrolled, we have to wait until both axes have stopped
// to snap back to avoid a jarring effect.
boolean waitingToSnapX = mX.getFlingState() == Axis.FlingStates.WAITING_TO_SNAP;
boolean waitingToSnapY = mY.getFlingState() == Axis.FlingStates.WAITING_TO_SNAP;
if ((mX.getOverscroll() == Axis.Overscroll.PLUS || mX.getOverscroll() == Axis.Overscroll.MINUS) &&
(mY.getOverscroll() == Axis.Overscroll.PLUS || mY.getOverscroll() == Axis.Overscroll.MINUS))
{
if (waitingToSnapX && waitingToSnapY) {
mX.startSnap(); mY.startSnap();
}
} else {
if (waitingToSnapX)
mX.startSnap();
if (waitingToSnapY)
mY.startSnap();
}
mX.displace(); mY.displace();
updatePosition();
if (mX.getFlingState() == Axis.FlingStates.STOPPED &&
mY.getFlingState() == Axis.FlingStates.STOPPED) {
stop();
}
}
private void stop() {
mState = PanZoomState.NOTHING;
if (mFlingTimer != null) {
mFlingTimer.cancel();
mFlingTimer = null;
}
}
}
private float computeElasticity(float excess, float viewportLength) {
return 1.0f - excess / (viewportLength * SNAP_LIMIT);
}
// Physics information for one axis (X or Y).
private static class Axis {
public enum FlingStates {
STOPPED,
SCROLLING,
WAITING_TO_SNAP,
SNAPPING,
}
public enum Overscroll {
NONE,
MINUS, // Overscrolled in the negative direction
PLUS, // Overscrolled in the positive direction
BOTH, // Overscrolled in both directions (page is zoomed to smaller than screen)
}
public float touchPos; /* Position of the last touch. */
public float velocity; /* Velocity in this direction. */
private FlingStates mFlingState; /* The fling state we're in on this axis. */
private EaseOutAnimation mSnapAnim; /* The animation when the page is snapping back. */
/* These three need to be kept in sync with the layer controller. */
public float viewportPos;
private float mViewportLength;
private int mScreenLength;
private int mPageLength;
public FlingStates getFlingState() { return mFlingState; }
public void setViewportLength(float viewportLength) { mViewportLength = viewportLength; }
public void setScreenLength(int screenLength) { mScreenLength = screenLength; }
public void setPageLength(int pageLength) { mPageLength = pageLength; }
private float getViewportEnd() { return viewportPos + mViewportLength; }
public Overscroll getOverscroll() {
boolean minus = (viewportPos < 0.0f);
boolean plus = (getViewportEnd() > mPageLength);
if (minus && plus)
return Overscroll.BOTH;
else if (minus)
return Overscroll.MINUS;
else if (plus)
return Overscroll.PLUS;
else
return Overscroll.NONE;
}
// Returns the amount that the page has been overscrolled. If the page hasn't been
// overscrolled on this axis, returns 0.
private float getExcess() {
switch (getOverscroll()) {
case MINUS: return Math.min(-viewportPos, mPageLength - getViewportEnd());
case PLUS: return Math.min(viewportPos, getViewportEnd() - mPageLength);
default: return 0.0f;
}
}
// Applies resistance along the edges when tracking.
public void applyEdgeResistance() {
float excess = getExcess();
if (excess > 0.0f)
velocity *= SNAP_LIMIT - excess / mViewportLength;
}
public void startFling() { mFlingState = FlingStates.SCROLLING; }
// Advances a fling animation by one step.
public void advanceFling() {
switch (mFlingState) {
case SCROLLING:
scroll();
return;
case WAITING_TO_SNAP:
// We don't do anything until the controller switches us into the snapping state.
return;
case SNAPPING:
snap();
return;
}
}
// Performs one frame of a scroll operation if applicable.
private void scroll() {
// If we aren't overscrolled, just apply friction.
float excess = getExcess();
if (excess == 0.0f) {
velocity *= FRICTION;
if (Math.abs(velocity) < 0.1f) {
velocity = 0.0f;
mFlingState = FlingStates.STOPPED;
}
return;
}
// Otherwise, decrease the velocity linearly.
float elasticity = 1.0f - excess / (mViewportLength * SNAP_LIMIT);
if (getOverscroll() == Overscroll.MINUS)
velocity = Math.min((velocity + OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
else // must be Overscroll.PLUS
velocity = Math.max((velocity - OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
if (Math.abs(velocity) < 0.3f) {
velocity = 0.0f;
mFlingState = FlingStates.WAITING_TO_SNAP;
}
}
// Starts a snap-into-place operation.
public void startSnap() {
switch (getOverscroll()) {
case MINUS:
mSnapAnim = new EaseOutAnimation(viewportPos, viewportPos + getExcess());
break;
case PLUS:
mSnapAnim = new EaseOutAnimation(viewportPos, viewportPos - getExcess());
break;
default:
throw new RuntimeException("Not overscrolled at startSnap()");
}
mFlingState = FlingStates.SNAPPING;
}
// Performs one frame of a snap-into-place operation.
private void snap() {
mSnapAnim.advance();
viewportPos = mSnapAnim.getPosition();
if (mSnapAnim.getFinished()) {
mSnapAnim = null;
mFlingState = FlingStates.STOPPED;
}
}
// Performs displacement of the viewport position according to the current velocity.
public void displace() { viewportPos += velocity; }
}
private static class EaseOutAnimation {
private float[] mFrames;
private float mPosition;
private float mOrigin;
private float mDest;
private long mTimestamp;
private boolean mFinished;
public EaseOutAnimation(float position, float dest) {
mPosition = mOrigin = position;
mDest = dest;
mFrames = new float[SNAP_TIME];
mTimestamp = System.currentTimeMillis();
mFinished = false;
plot(position, dest, mFrames);
}
public float getPosition() { return mPosition; }
public boolean getFinished() { return mFinished; }
private void advance() {
int frame = (int)(System.currentTimeMillis() - mTimestamp);
if (frame >= SNAP_TIME) {
mPosition = mDest;
mFinished = true;
return;
}
mPosition = mFrames[frame];
}
private static void plot(float from, float to, float[] frames) {
int nextX = 0;
for (int i = 0; i < SUBDIVISION_COUNT; i++) {
float t = (float)i / (float)SUBDIVISION_COUNT;
float xPos = (3.0f*t*t - 2.0f*t*t*t) * (float)frames.length;
if ((int)xPos < nextX)
continue;
int oldX = nextX;
nextX = (int)xPos;
float yPos = 1.74f*t*t - 0.74f*t*t*t;
float framePos = from + (to - from) * yPos;
while (oldX < nextX)
frames[oldX++] = framePos;
if (nextX >= frames.length)
break;
}
// Pad out any remaining frames.
while (nextX < frames.length) {
frames[nextX] = frames[nextX - 1];
nextX++;
}
}
}
/*
* Zooming
*/
@Override
public boolean onScale(ScaleGestureDetector detector) {
mState = PanZoomState.PINCHING;
float newZoom = detector.getCurrentSpan() / mInitialZoomSpan;
IntSize screenSize = mController.getScreenSize();
float x = mInitialZoomFocus.x - (detector.getFocusX() / newZoom);
float y = mInitialZoomFocus.y - (detector.getFocusY() / newZoom);
float width = screenSize.width / newZoom;
float height = screenSize.height / newZoom;
mController.setVisibleRect(x, y, width, height);
mController.notifyLayerClientOfGeometryChange();
populatePositionAndLength();
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mState = PanZoomState.PINCHING;
FloatRect initialZoomRect = mController.getVisibleRect();
float initialZoom = mController.getZoomFactor();
mInitialZoomFocus =
new FloatPoint(initialZoomRect.x + (detector.getFocusX() / initialZoom),
initialZoomRect.y + (detector.getFocusY() / initialZoom));
mInitialZoomSpan = detector.getCurrentSpan() / initialZoom;
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
mState = PanZoomState.PANNING_HOLD;
mLastTimestamp = System.currentTimeMillis();
mX.touchPos = detector.getFocusX();
mY.touchPos = detector.getFocusY();
}
@Override
public void onLongPress(MotionEvent motionEvent) {
JSONObject ret = new JSONObject();
try {
FloatPoint point = new FloatPoint(motionEvent.getX(), motionEvent.getY());
point = mController.convertViewPointToLayerPoint(point);
ret.put("x", (int)Math.round(point.x));
ret.put("y", (int)Math.round(point.y));
} catch(Exception ex) {
Log.w(LOG_NAME, "Error building return: " + ex);
}
GeckoEvent e = new GeckoEvent("Gesture:LongPress", ret.toString());
GeckoAppShell.sendEventToGecko(e);
}
}

View File

@ -0,0 +1,112 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.ui;
import org.mozilla.gecko.gfx.FloatPoint;
import org.mozilla.gecko.gfx.FloatRect;
import org.mozilla.gecko.gfx.IntPoint;
import org.mozilla.gecko.gfx.IntRect;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerController;
/** Manages the dimensions of the page viewport. */
public class ViewportController {
private IntSize mPageSize;
private FloatRect mVisibleRect;
public ViewportController(IntSize pageSize, FloatRect visibleRect) {
mPageSize = pageSize;
mVisibleRect = visibleRect;
}
private float clamp(float min, float value, float max) {
if (max < min)
return min;
return (value < min) ? min : (value > max) ? max : value;
}
/** Returns the given rect, clamped to the boundaries of a tile. */
public FloatRect clampRect(FloatRect rect) {
float x = clamp(0, rect.x, mPageSize.width - LayerController.TILE_WIDTH);
float y = clamp(0, rect.y, mPageSize.height - LayerController.TILE_HEIGHT);
return new FloatRect(x, y, rect.width, rect.height);
}
/** Returns the coordinates of a tile centered on the given rect. */
public static FloatRect widenRect(FloatRect rect) {
FloatPoint center = rect.getCenter();
return new FloatRect(center.x - LayerController.TILE_WIDTH / 2,
center.y - LayerController.TILE_HEIGHT / 2,
LayerController.TILE_WIDTH,
LayerController.TILE_HEIGHT);
}
/**
* Given the layer controller's visible rect, page size, and screen size, returns the zoom
* factor.
*/
public float getZoomFactor(FloatRect layerVisibleRect, IntSize layerPageSize,
IntSize screenSize) {
FloatRect transformed = transformVisibleRect(layerVisibleRect, layerPageSize);
return (float)screenSize.width / transformed.width;
}
/**
* Given the visible rectangle that the user is viewing and the layer controller's page size,
* returns the dimensions of the box that this corresponds to on the page.
*/
public FloatRect transformVisibleRect(FloatRect layerVisibleRect, IntSize layerPageSize) {
float zoomFactor = (float)layerPageSize.width / (float)mPageSize.width;
return layerVisibleRect.scaleAll(1.0f / zoomFactor);
}
/**
* Given the visible rectangle that the user is viewing and the layer controller's page size,
* returns the dimensions in layer coordinates that this corresponds to.
*/
public FloatRect untransformVisibleRect(FloatRect viewportVisibleRect, IntSize layerPageSize) {
float zoomFactor = (float)layerPageSize.width / (float)mPageSize.width;
return viewportVisibleRect.scaleAll(zoomFactor);
}
public IntSize getPageSize() { return mPageSize; }
public void setPageSize(IntSize pageSize) { mPageSize = pageSize; }
public FloatRect getVisibleRect() { return mVisibleRect; }
public void setVisibleRect(FloatRect visibleRect) { mVisibleRect = visibleRect; }
}

View File

@ -1869,6 +1869,7 @@ CreateSurfaceForWindow(nsIWidget *aWidget, EGLConfig config)
{
EGLSurface surface;
#ifdef PCWALTON_BROKEN
#ifdef DEBUG
sEGLLibrary.DumpEGLConfig(config);
@ -1887,6 +1888,8 @@ CreateSurfaceForWindow(nsIWidget *aWidget, EGLConfig config)
printf_stderr("got surface %p\n", surface);
#else
surface = sEGLLibrary.fCreateWindowSurface(EGL_DISPLAY(), config, GET_NATIVE_WINDOW(aWidget), 0);
#endif
#endif
return surface;

View File

@ -76,6 +76,20 @@ const kMaxKineticSpeed = 9;
// it's larger than this, lock the slow-moving axis.
const kAxisLockRatio = 5;
// The element tag names that are considered to receive input. Mouse-down
// events directed to one of these are allowed to go through.
const kElementsReceivingInput = {
applet: true,
audio: true,
button: true,
embed: true,
input: true,
map: true,
select: true,
textarea: true,
video: true
};
function dump(a) {
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
}
@ -146,6 +160,7 @@ var BrowserApp = {
Services.obs.addObserver(this, "Preferences:Set", false);
Services.obs.addObserver(this, "ScrollTo:FocusedInput", false);
Services.obs.addObserver(this, "Sanitize:ClearAll", false);
Services.obs.addObserver(this, "PanZoom:PanZoom", false);
Services.obs.addObserver(this, "FullScreen:Exit", false);
Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
@ -255,6 +270,12 @@ var BrowserApp = {
return null;
},
getPageSizeForBrowser: function getPageSizeForBrowser(aBrowser) {
let html = aBrowser.contentDocument.documentElement;
let body = aBrowser.contentDocument.body;
return { width: body.scrollWidth, height: body.scrollHeight };
},
loadURI: function loadURI(aURI, aParams) {
let browser = this.selectedBrowser;
if (!browser)
@ -478,6 +499,20 @@ var BrowserApp = {
focused.scrollIntoView(false);
},
panZoom: function(aData) {
let data = JSON.parse(aData);
let browser = this.selectedBrowser;
/*let documentElement = browser.contentDocument.documentElement;
documentElement.style.MozTransform = 'translate(-' + data.x + 'px, -' + data.y + 'px) ' +
'translate(-50%, -50%) scale(' + data.zoomFactor + ') ' +
'translate(50%, 50%)';*/
browser.contentWindow.scrollTo(data.x, data.y);
sendMessageToJava({ gecko: { type: "PanZoom:Ack", rect: data } });
},
updateScrollbarsFor: function(aElement) {
// only draw the scrollbars if we're scrolling the root content element
let doc = this.selectedBrowser.contentDocument;
@ -518,6 +553,13 @@ var BrowserApp = {
this.horizScroller.setAttribute("panning", "");
},
/* FIXME: Awful hack to tide us over until the display port is usable. */
fakeDisplayPort: function(aBrowser) {
let html = aBrowser.contentDocument.documentElement;
html.style.width = '980px';
html.style.height = '1500px';
},
observe: function(aSubject, aTopic, aData) {
let browser = this.selectedBrowser;
if (!browser)
@ -552,6 +594,8 @@ var BrowserApp = {
this.scrollToFocusedInput(browser);
} else if (aTopic == "Sanitize:ClearAll") {
Sanitizer.sanitize();
} else if (aTopic == "PanZoom:PanZoom") {
this.panZoom(aData);
} else if (aTopic == "FullScreen:Exit") {
browser.contentDocument.mozCancelFullScreen();
}
@ -896,6 +940,8 @@ Tab.prototype = {
this.browser = document.createElement("browser");
this.browser.setAttribute("type", "content");
this.browser.setAttribute("width", "980");
this.browser.setAttribute("height", "480");
BrowserApp.deck.appendChild(this.browser);
this.browser.stop();
@ -971,7 +1017,7 @@ Tab.prototype = {
state: aStateFlags
}
};
sendMessageToJava(message);
}
},
@ -1080,12 +1126,11 @@ var BrowserEventHandler = {
window.addEventListener("mouseup", this, true);
window.addEventListener("mousemove", this, true);
BrowserApp.deck.addEventListener("MozMagnifyGestureStart", this, true);
BrowserApp.deck.addEventListener("MozMagnifyGestureUpdate", this, true);
BrowserApp.deck.addEventListener("DOMContentLoaded", this, true);
BrowserApp.deck.addEventListener("DOMLinkAdded", this, true);
BrowserApp.deck.addEventListener("DOMTitleChanged", this, true);
BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
BrowserApp.deck.addEventListener("MozScrolledAreaChanged", this, true);
},
handleEvent: function(aEvent) {
@ -1118,6 +1163,9 @@ var BrowserEventHandler = {
browser.removeEventListener("pagehide", listener, true);
}, true);
}
BrowserApp.fakeDisplayPort(browser);
break;
}
@ -1205,6 +1253,8 @@ var BrowserEventHandler = {
if (this.panElement)
this.panning = true;
if (!this._elementReceivesInput(aEvent.target))
aEvent.preventDefault(); // Stops selection.
break;
case "mousemove":
@ -1281,9 +1331,6 @@ var BrowserEventHandler = {
break;
case "mouseup":
if (!this.panning)
break;
this.panning = false;
// hide the scrollbars in case we're done scrolling. if the
@ -1471,41 +1518,22 @@ var BrowserEventHandler = {
}
break;
case "MozMagnifyGestureStart":
this._pinchDelta = 0;
this.zoomCallbackFired = true;
break;
case "MozScrolledAreaChanged":
dump("### Resize!");
case "MozMagnifyGestureUpdate":
if (!aEvent.delta)
break;
this._pinchDelta += aEvent.delta;
if ((Math.abs(this._pinchDelta) >= 1) && this.zoomCallbackFired) {
// pinchDelta is the difference in pixels since the last call, so can
// be viewed as the number of extra/fewer pixels visible.
//
// We can work out the new zoom level by looking at the window width
// and height, and the existing zoom level.
let currentZoom = BrowserApp.selectedBrowser.markupDocumentViewer.fullZoom;
let currentSize = Math.sqrt(Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2));
let newZoom = ((currentSize * currentZoom) + this._pinchDelta) / currentSize;
let self = this;
let callback = {
onBeforePaint: function zoomCallback(timeStamp) {
BrowserApp.selectedBrowser.markupDocumentViewer.fullZoom = newZoom;
self.zoomCallbackFired = true;
}
};
this._pinchDelta = 0;
// Use mozRequestAnimationFrame to stop from flooding fullZoom
this.zoomCallbackFired = false;
window.mozRequestAnimationFrame(callback);
/* TODO: Only for tab in foreground */
let browser = BrowserApp.getBrowserForDocument(aEvent.target);
if (!browser) {
dump("### Resize: No browser!");
return;
}
sendMessageToJava({
gecko: {
type: "PanZoom:Resize",
size: BrowserApp.getPageSizeForBrowser(browser)
}
});
break;
}
},
@ -1560,16 +1588,14 @@ var BrowserEventHandler = {
/* Element is scrollable if its scroll-size exceeds its client size, and:
* - It has overflow 'auto' or 'scroll'
* - It's a textarea
* - It's an HTML/BODY node
* We don't consider HTML/BODY nodes here, since Java pans those.
*/
if (checkElem) {
if (((elem.scrollHeight > elem.clientHeight) ||
(elem.scrollWidth > elem.clientWidth)) &&
(elem.style.overflow == 'auto' ||
elem.style.overflow == 'scroll' ||
elem.localName == 'textarea' ||
elem.localName == 'html' ||
elem.localName == 'body')) {
elem.localName == 'textarea')) {
scrollable = true;
break;
}
@ -1591,6 +1617,12 @@ var BrowserEventHandler = {
return elem;
},
_elementReceivesInput: function(aElement) {
return aElement instanceof Element &&
(kElementsReceivingInput.hasOwnProperty(aElement.tagName.toLowerCase()) ||
aElement.contentEditable === "true" || aElement.contentEditable === "");
},
_scrollElementBy: function(elem, x, y) {
elem.scrollTop = elem.scrollTop + y;
elem.scrollLeft = elem.scrollLeft + x;

View File

@ -233,6 +233,7 @@ SHELL_WRAPPER1(nativeRun, jstring)
SHELL_WRAPPER1(notifyGeckoOfEvent, jobject)
SHELL_WRAPPER0(processNextNativeEvent)
SHELL_WRAPPER1(setSurfaceView, jobject)
SHELL_WRAPPER1(setSoftwareLayerClient, jobject)
SHELL_WRAPPER0(onResume)
SHELL_WRAPPER0(onLowMemory)
SHELL_WRAPPER3(callObserver, jstring, jstring, jstring)
@ -640,6 +641,7 @@ loadLibs(const char *apkName)
GETFUNC(notifyGeckoOfEvent);
GETFUNC(processNextNativeEvent);
GETFUNC(setSurfaceView);
GETFUNC(setSoftwareLayerClient);
GETFUNC(onResume);
GETFUNC(onLowMemory);
GETFUNC(callObserver);

View File

@ -772,9 +772,9 @@ AndroidBridge::GetAccessibilityEnabled()
}
void
AndroidBridge::SetSurfaceView(jobject obj)
AndroidBridge::SetSoftwareLayerClient(jobject obj)
{
mSurfaceView.Init(obj);
mSoftwareLayerClient.Init(obj);
}
void
@ -784,51 +784,6 @@ AndroidBridge::ShowInputMethodPicker()
mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jShowInputMethodPicker);
}
void *
AndroidBridge::CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoSurfaceView &sview)
{
ALOG_BRIDGE("AndroidBridge::CallEglCreateWindowSurface");
AutoLocalJNIFrame jniFrame;
/*
* This is basically:
*
* s = EGLContext.getEGL().eglCreateWindowSurface(new EGLDisplayImpl(dpy),
* new EGLConfigImpl(config),
* view.getHolder(), null);
* return s.mEGLSurface;
*
* We can't do it from java, because the EGLConfigImpl constructor is private.
*/
jobject surfaceHolder = sview.GetSurfaceHolder();
if (!surfaceHolder)
return nsnull;
// grab some fields and methods we'll need
jmethodID constructConfig = mJNIEnv->GetMethodID(jEGLConfigImplClass, "<init>", "(I)V");
jmethodID constructDisplay = mJNIEnv->GetMethodID(jEGLDisplayImplClass, "<init>", "(I)V");
jmethodID getEgl = mJNIEnv->GetStaticMethodID(jEGLContextClass, "getEGL", "()Ljavax/microedition/khronos/egl/EGL;");
jmethodID createWindowSurface = mJNIEnv->GetMethodID(jEGL10Class, "eglCreateWindowSurface", "(Ljavax/microedition/khronos/egl/EGLDisplay;Ljavax/microedition/khronos/egl/EGLConfig;Ljava/lang/Object;[I)Ljavax/microedition/khronos/egl/EGLSurface;");
jobject egl = mJNIEnv->CallStaticObjectMethod(jEGLContextClass, getEgl);
jobject jdpy = mJNIEnv->NewObject(jEGLDisplayImplClass, constructDisplay, (int) dpy);
jobject jconf = mJNIEnv->NewObject(jEGLConfigImplClass, constructConfig, (int) config);
// make the call
jobject surf = mJNIEnv->CallObjectMethod(egl, createWindowSurface, jdpy, jconf, surfaceHolder, NULL);
if (!surf)
return nsnull;
jfieldID sfield = mJNIEnv->GetFieldID(jEGLSurfaceImplClass, "mEGLSurface", "I");
jint realSurface = mJNIEnv->GetIntField(surf, sfield);
return (void*) realSurface;
}
bool
AndroidBridge::GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt)
{

View File

@ -40,6 +40,7 @@
#include <jni.h>
#include <android/log.h>
#include <cstdlib>
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
@ -149,8 +150,8 @@ public:
void ScheduleRestart();
void SetSurfaceView(jobject jobj);
AndroidGeckoSurfaceView& SurfaceView() { return mSurfaceView; }
void SetSoftwareLayerClient(jobject jobj);
AndroidGeckoSoftwareLayerClient &GetSoftwareLayerClient() { return mSoftwareLayerClient; }
bool GetHandlersForURL(const char *aURL,
nsIMutableArray* handlersArray = nsnull,
@ -247,9 +248,6 @@ public:
int mEntries;
};
/* See GLHelpers.java as to why this is needed */
void *CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoSurfaceView& surfaceView);
bool GetStaticStringField(const char *classID, const char *field, nsAString &result);
bool GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt);
@ -314,8 +312,8 @@ protected:
JNIEnv *mJNIEnv;
void *mThread;
// the GeckoSurfaceView
AndroidGeckoSurfaceView mSurfaceView;
// the software rendering layer client
AndroidGeckoSoftwareLayerClient mSoftwareLayerClient;
// the GeckoAppShell java class
jclass mGeckoAppShellClass;

View File

@ -44,6 +44,7 @@
#include <jni.h>
#include <pthread.h>
#include <dlfcn.h>
#include <stdio.h>
#include "nsAppShell.h"
#include "nsWindow.h"
@ -67,7 +68,7 @@ extern "C" {
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_nativeInit(JNIEnv *, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoOfEvent(JNIEnv *, jclass, jobject event);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_processNextNativeEvent(JNIEnv *, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setSurfaceView(JNIEnv *jenv, jclass, jobject sv);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setSoftwareLayerClient(JNIEnv *jenv, jclass, jobject sv);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onResume(JNIEnv *, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onLowMemory(JNIEnv *, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_callObserver(JNIEnv *, jclass, jstring observerKey, jstring topic, jstring data);
@ -87,6 +88,7 @@ extern "C" {
NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_nativeInit(JNIEnv *jenv, jclass jc)
{
ALOG("Native init!");
AndroidBridge::ConstructBridge(jenv, jc);
}
@ -94,8 +96,9 @@ NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoOfEvent(JNIEnv *jenv, jclass jc, jobject event)
{
// poke the appshell
if (nsAppShell::gAppShell)
if (nsAppShell::gAppShell) {
nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(jenv, event));
}
}
NS_EXPORT void JNICALL
@ -107,9 +110,11 @@ Java_org_mozilla_gecko_GeckoAppShell_processNextNativeEvent(JNIEnv *jenv, jclass
}
NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_setSurfaceView(JNIEnv *jenv, jclass, jobject obj)
Java_org_mozilla_gecko_GeckoAppShell_setSoftwareLayerClient(JNIEnv *jenv, jclass, jobject obj)
{
AndroidBridge::Bridge()->SetSurfaceView(jenv->NewGlobalRef(obj));
ALOG("setSoftwareLayerClient before");
AndroidBridge::Bridge()->SetSoftwareLayerClient(jenv->NewGlobalRef(obj));
ALOG("setSoftwareLayerClient after");
}
NS_EXPORT void JNICALL

View File

@ -103,15 +103,11 @@ jmethodID AndroidAddress::jGetSubLocalityMethod;
jmethodID AndroidAddress::jGetSubThoroughfareMethod;
jmethodID AndroidAddress::jGetThoroughfareMethod;
jclass AndroidGeckoSurfaceView::jGeckoSurfaceViewClass = 0;
jmethodID AndroidGeckoSurfaceView::jBeginDrawingMethod = 0;
jmethodID AndroidGeckoSurfaceView::jEndDrawingMethod = 0;
jmethodID AndroidGeckoSurfaceView::jDraw2DBitmapMethod = 0;
jmethodID AndroidGeckoSurfaceView::jDraw2DBufferMethod = 0;
jmethodID AndroidGeckoSurfaceView::jGetSoftwareDrawBitmapMethod = 0;
jmethodID AndroidGeckoSurfaceView::jGetSoftwareDrawBufferMethod = 0;
jmethodID AndroidGeckoSurfaceView::jGetSurfaceMethod = 0;
jmethodID AndroidGeckoSurfaceView::jGetHolderMethod = 0;
jclass AndroidGeckoSoftwareLayerClient::jGeckoSoftwareLayerClientClass = 0;
jmethodID AndroidGeckoSoftwareLayerClient::jLockBufferMethod = 0;
jmethodID AndroidGeckoSoftwareLayerClient::jUnlockBufferMethod = 0;
jmethodID AndroidGeckoSoftwareLayerClient::jBeginDrawingMethod = 0;
jmethodID AndroidGeckoSoftwareLayerClient::jEndDrawingMethod = 0;
#define JNI() (AndroidBridge::JNI())
@ -131,10 +127,11 @@ void
mozilla::InitAndroidJavaWrappers(JNIEnv *jEnv)
{
AndroidGeckoEvent::InitGeckoEventClass(jEnv);
AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(jEnv);
AndroidPoint::InitPointClass(jEnv);
AndroidRect::InitRectClass(jEnv);
AndroidLocation::InitLocationClass(jEnv);
AndroidAddress::InitAddressClass(jEnv);
AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(jEnv);
}
void
@ -173,23 +170,6 @@ AndroidGeckoEvent::InitGeckoEventClass(JNIEnv *jEnv)
jAddressField = getField("mAddress", "Landroid/location/Address;");
}
void
AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(JNIEnv *jEnv)
{
initInit();
jGeckoSurfaceViewClass = getClassGlobalRef("org/mozilla/gecko/GeckoSurfaceView");
jBeginDrawingMethod = getMethod("beginDrawing", "()I");
jGetSoftwareDrawBitmapMethod = getMethod("getSoftwareDrawBitmap", "()Landroid/graphics/Bitmap;");
jGetSoftwareDrawBufferMethod = getMethod("getSoftwareDrawBuffer", "()Ljava/nio/ByteBuffer;");
jEndDrawingMethod = getMethod("endDrawing", "()V");
jDraw2DBitmapMethod = getMethod("draw2D", "(Landroid/graphics/Bitmap;II)V");
jDraw2DBufferMethod = getMethod("draw2D", "(Ljava/nio/ByteBuffer;I)V");
jGetSurfaceMethod = getMethod("getSurface", "()Landroid/view/Surface;");
jGetHolderMethod = getMethod("getHolder", "()Landroid/view/SurfaceHolder;");
}
void
AndroidLocation::InitLocationClass(JNIEnv *jEnv)
{
@ -304,6 +284,20 @@ AndroidRect::InitRectClass(JNIEnv *jEnv)
jRightField = getField("right", "I");
}
void
AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv)
{
initInit();
jGeckoSoftwareLayerClientClass =
getClassGlobalRef("org/mozilla/gecko/gfx/GeckoSoftwareLayerClient");
jLockBufferMethod = getMethod("lockBuffer", "()Ljava/nio/ByteBuffer;");
jUnlockBufferMethod = getMethod("unlockBuffer", "()V");
jBeginDrawingMethod = getMethod("beginDrawing", "()V");
jEndDrawingMethod = getMethod("endDrawing", "(IIII)V");
}
#undef initInit
#undef initClassGlobalRef
#undef getField
@ -423,7 +417,9 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jobject jobj)
break;
case DRAW:
ALOG("### Draw, before ReadRectField");
ReadRectField(jenv);
ALOG("### Draw, after ReadRectField");
break;
case ORIENTATION_EVENT:
@ -480,10 +476,10 @@ AndroidGeckoEvent::Init(int aType)
}
void
AndroidGeckoEvent::Init(int x1, int y1, int x2, int y2)
AndroidGeckoEvent::Init(int aType, const nsIntRect &aRect)
{
mType = DRAW;
mRect.SetEmpty();
mType = aType;
mRect = aRect;
}
void
@ -499,64 +495,6 @@ AndroidGeckoEvent::Init(AndroidGeckoEvent *aResizeEvent)
mP1.y = aResizeEvent->mP1.y;
}
void
AndroidGeckoSurfaceView::Init(jobject jobj)
{
NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
wrapped_obj = jobj;
}
int
AndroidGeckoSurfaceView::BeginDrawing()
{
NS_ASSERTION(!isNull(), "BeginDrawing called on null surfaceview!");
return JNI()->CallIntMethod(wrapped_obj, jBeginDrawingMethod);
}
void
AndroidGeckoSurfaceView::EndDrawing()
{
JNI()->CallVoidMethod(wrapped_obj, jEndDrawingMethod);
}
void
AndroidGeckoSurfaceView::Draw2D(jobject bitmap, int width, int height)
{
JNI()->CallVoidMethod(wrapped_obj, jDraw2DBitmapMethod, bitmap, width, height);
}
void
AndroidGeckoSurfaceView::Draw2D(jobject buffer, int stride)
{
JNI()->CallVoidMethod(wrapped_obj, jDraw2DBufferMethod, buffer, stride);
}
jobject
AndroidGeckoSurfaceView::GetSoftwareDrawBitmap()
{
return JNI()->CallObjectMethod(wrapped_obj, jGetSoftwareDrawBitmapMethod);
}
jobject
AndroidGeckoSurfaceView::GetSoftwareDrawBuffer()
{
return JNI()->CallObjectMethod(wrapped_obj, jGetSoftwareDrawBufferMethod);
}
jobject
AndroidGeckoSurfaceView::GetSurface()
{
return JNI()->CallObjectMethod(wrapped_obj, jGetSurfaceMethod);
}
jobject
AndroidGeckoSurfaceView::GetSurfaceHolder()
{
return JNI()->CallObjectMethod(wrapped_obj, jGetHolderMethod);
}
void
AndroidPoint::Init(JNIEnv *jenv, jobject jobj)
{
@ -574,17 +512,72 @@ AndroidPoint::Init(JNIEnv *jenv, jobject jobj)
}
void
AndroidRect::Init(JNIEnv *jenv, jobject jobj)
AndroidGeckoSoftwareLayerClient::Init(jobject jobj)
{
NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
wrapped_obj = jobj;
}
jobject
AndroidGeckoSoftwareLayerClient::LockBuffer()
{
NS_ASSERTION(!isNull(), "LockBuffer() called on null software layer client!");
return JNI()->CallObjectMethod(wrapped_obj, jLockBufferMethod);
}
unsigned char *
AndroidGeckoSoftwareLayerClient::LockBufferBits()
{
return reinterpret_cast<unsigned char *>(JNI()->GetDirectBufferAddress(LockBuffer()));
}
void
AndroidGeckoSoftwareLayerClient::UnlockBuffer()
{
NS_ASSERTION(!isNull(), "UnlockBuffer() called on null software layer client!");
JNI()->CallVoidMethod(wrapped_obj, jUnlockBufferMethod);
}
void
AndroidGeckoSoftwareLayerClient::BeginDrawing()
{
NS_ASSERTION(!isNull(), "BeginDrawing() called on null software layer client!");
return JNI()->CallVoidMethod(wrapped_obj, jBeginDrawingMethod);
}
void
AndroidGeckoSoftwareLayerClient::EndDrawing(const nsIntRect &aRect)
{
NS_ASSERTION(!isNull(), "EndDrawing() called on null software layer client!");
return JNI()->CallVoidMethod(wrapped_obj, jEndDrawingMethod, aRect.x, aRect.y, aRect.width,
aRect.height);
}
void
AndroidRect::Init(JNIEnv *jenv, jobject jobj)
{
NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
ALOG("AndroidRect::Init point a");
wrapped_obj = jobj;
ALOG("AndroidRect::Init point b");
if (jobj) {
ALOG("AndroidRect::Init point c");
mTop = jenv->GetIntField(jobj, jTopField);
mLeft = jenv->GetIntField(jobj, jLeftField);
mRight = jenv->GetIntField(jobj, jRightField);
mBottom = jenv->GetIntField(jobj, jBottomField);
ALOG("AndroidRect::Init point d");
} else {
mTop = 0;
mLeft = 0;

View File

@ -46,7 +46,7 @@
#include "nsRect.h"
#include "nsString.h"
//#define FORCE_ALOG 1
#define FORCE_ALOG 1
#ifndef ALOG
#if defined(DEBUG) || defined(FORCE_ALOG)
@ -149,48 +149,27 @@ protected:
static jfieldID jTopField;
};
class AndroidGeckoSurfaceView : public WrappedJavaObject
{
class AndroidGeckoSoftwareLayerClient : public WrappedJavaObject {
public:
static void InitGeckoSurfaceViewClass(JNIEnv *jEnv);
AndroidGeckoSurfaceView() { }
AndroidGeckoSurfaceView(jobject jobj) {
Init(jobj);
}
static void InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv);
void Init(jobject jobj);
enum {
DRAW_ERROR = 0,
DRAW_GLES_2 = 1,
DRAW_2D = 2,
DRAW_DISABLED = 3
};
AndroidGeckoSoftwareLayerClient() {}
AndroidGeckoSoftwareLayerClient(jobject jobj) { Init(jobj); }
int BeginDrawing();
jobject GetSoftwareDrawBitmap();
jobject GetSoftwareDrawBuffer();
void EndDrawing();
void Draw2D(jobject bitmap, int width, int height);
void Draw2D(jobject buffer, int stride);
jobject LockBuffer();
unsigned char *LockBufferBits();
void UnlockBuffer();
void BeginDrawing();
void EndDrawing(const nsIntRect &aRect);
jobject GetSurface();
// must have a JNI local frame when calling this,
// and you'd better know what you're doing
jobject GetSurfaceHolder();
protected:
static jclass jGeckoSurfaceViewClass;
private:
static jclass jGeckoSoftwareLayerClientClass;
static jmethodID jLockBufferMethod;
static jmethodID jUnlockBufferMethod;
static jmethodID jBeginDrawingMethod;
static jmethodID jEndDrawingMethod;
static jmethodID jDraw2DBitmapMethod;
static jmethodID jDraw2DBufferMethod;
static jmethodID jGetSoftwareDrawBitmapMethod;
static jmethodID jGetSoftwareDrawBufferMethod;
static jmethodID jGetSurfaceMethod;
static jmethodID jGetHolderMethod;
};
class AndroidKeyEvent
@ -385,8 +364,8 @@ public:
AndroidGeckoEvent(int aType) {
Init(aType);
}
AndroidGeckoEvent(int x1, int y1, int x2, int y2) {
Init(x1, y1, x2, y2);
AndroidGeckoEvent(int aType, const nsIntRect &aRect) {
Init(aType, aRect);
}
AndroidGeckoEvent(JNIEnv *jenv, jobject jobj) {
Init(jenv, jobj);
@ -397,7 +376,7 @@ public:
void Init(JNIEnv *jenv, jobject jobj);
void Init(int aType);
void Init(int x1, int y1, int x2, int y2);
void Init(int aType, const nsIntRect &aRect);
void Init(AndroidGeckoEvent *aResizeEvent);
int Action() { return mAction; }

View File

@ -60,6 +60,8 @@
#include "prlog.h"
#endif
#define DEBUG_ANDROID_EVENTS 1
#ifdef DEBUG_ANDROID_EVENTS
#define EVLOG(args...) ALOG(args)
#else

View File

@ -76,6 +76,12 @@ using mozilla::unused;
#include "imgIEncoder.h"
#include "nsStringGlue.h"
// NB: Keep these in sync with LayerController.java in embedding/android/.
#define TILE_WIDTH 1024
#define TILE_HEIGHT 2048
using namespace mozilla;
NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
@ -135,7 +141,6 @@ static nsRefPtr<gl::GLContext> sGLContext;
static bool sFailedToCreateGLContext = false;
static bool sValidSurface;
static bool sSurfaceExists = false;
static void *sNativeWindow = nsnull;
// Multitouch swipe thresholds in inches
static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4;
@ -296,6 +301,15 @@ nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>& config)
return NS_OK;
}
void
nsWindow::RedrawAll()
{
nsIntRect entireRect(0, 0, TILE_WIDTH, TILE_HEIGHT);
AndroidGeckoEvent *event = new AndroidGeckoEvent(AndroidGeckoEvent::DRAW,
entireRect);
nsAppShell::gAppShell->PostEvent(event);
}
NS_IMETHODIMP
nsWindow::SetParent(nsIWidget *aNewParent)
{
@ -316,7 +330,7 @@ nsWindow::SetParent(nsIWidget *aNewParent)
// if we are now in the toplevel window's hierarchy, schedule a redraw
if (FindTopLevel() == TopWindow())
nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(-1, -1, -1, -1));
RedrawAll();
return NS_OK;
}
@ -384,7 +398,7 @@ nsWindow::Show(bool aState)
}
}
} else if (FindTopLevel() == TopWindow()) {
nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(-1, -1, -1, -1));
RedrawAll();
}
#ifdef ACCESSIBILITY
@ -511,7 +525,7 @@ nsWindow::Resize(PRInt32 aX,
// Should we skip honoring aRepaint here?
if (aRepaint && FindTopLevel() == TopWindow())
nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(-1, -1, -1, -1));
RedrawAll();
return NS_OK;
}
@ -564,7 +578,8 @@ NS_IMETHODIMP
nsWindow::Invalidate(const nsIntRect &aRect,
bool aIsSynchronous)
{
nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(-1, -1, -1, -1));
AndroidGeckoEvent *event = new AndroidGeckoEvent(AndroidGeckoEvent::DRAW, aRect);
nsAppShell::gAppShell->PostEvent(event);
return NS_OK;
}
@ -633,7 +648,7 @@ nsWindow::BringToFront()
// force a window resize
nsAppShell::gAppShell->ResendLastResizeEvent(this);
nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(-1, -1, -1, -1));
RedrawAll();
}
NS_IMETHODIMP
@ -797,7 +812,8 @@ nsWindow::DrawToFile(const nsAString &path)
return PR_FALSE;
}
bool result = DrawTo(imgSurface);
nsIntRect boundsRect(0, 0, mBounds.width, mBounds.height);
bool result = DrawTo(imgSurface, boundsRect);
NS_ENSURE_TRUE(result, PR_FALSE);
nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/png");
@ -957,27 +973,12 @@ nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae)
case AndroidGeckoEvent::SURFACE_CREATED:
sSurfaceExists = true;
if (AndroidBridge::Bridge()->HasNativeWindowAccess()) {
AndroidGeckoSurfaceView& sview(AndroidBridge::Bridge()->SurfaceView());
jobject surface = sview.GetSurface();
if (surface) {
sNativeWindow = AndroidBridge::Bridge()->AcquireNativeWindow(surface);
if (sNativeWindow) {
AndroidBridge::Bridge()->SetNativeWindowFormat(sNativeWindow, AndroidBridge::WINDOW_FORMAT_RGB_565);
}
}
}
break;
case AndroidGeckoEvent::SURFACE_DESTROYED:
if (sGLContext && sValidSurface) {
sGLContext->ReleaseSurface();
}
if (sNativeWindow) {
AndroidBridge::Bridge()->ReleaseNativeWindow(sNativeWindow);
sNativeWindow = nsnull;
}
sSurfaceExists = false;
sValidSurface = false;
break;
@ -1013,11 +1014,15 @@ nsWindow::OnAndroidEvent(AndroidGeckoEvent *ae)
}
bool
nsWindow::DrawTo(gfxASurface *targetSurface)
nsWindow::DrawTo(gfxASurface *targetSurface, const nsIntRect &invalidRect)
{
if (!mIsVisible)
return false;
nsWindowType windowType;
GetWindowType(windowType);
ALOG("Window type is %d", (int)windowType);
nsEventStatus status;
nsIntRect boundsRect(0, 0, mBounds.width, mBounds.height);
@ -1035,7 +1040,7 @@ nsWindow::DrawTo(gfxASurface *targetSurface)
// If we have no covering child, then we need to render this.
if (coveringChildIndex == -1) {
nsPaintEvent event(true, NS_PAINT, this);
event.region = boundsRect;
event.region = boundsRect.Intersect(invalidRect);
switch (GetLayerManager(nsnull)->GetBackendType()) {
case LayerManager::LAYERS_BASIC: {
nsRefPtr<gfxContext> ctx = new gfxContext(targetSurface);
@ -1090,7 +1095,7 @@ nsWindow::DrawTo(gfxASurface *targetSurface)
targetSurface->SetDeviceOffset(offset + gfxPoint(mChildren[i]->mBounds.x,
mChildren[i]->mBounds.y));
bool ok = mChildren[i]->DrawTo(targetSurface);
bool ok = mChildren[i]->DrawTo(targetSurface, invalidRect);
if (!ok) {
ALOG("nsWindow[%p]::DrawTo child %d[%p] returned FALSE!", (void*) this, i, (void*)mChildren[i]);
@ -1106,11 +1111,6 @@ nsWindow::DrawTo(gfxASurface *targetSurface)
void
nsWindow::OnDraw(AndroidGeckoEvent *ae)
{
if (!sSurfaceExists) {
return;
}
if (!IsTopLevel()) {
ALOG("##### redraw for window %p, which is not a toplevel window -- sending to toplevel!", (void*) this);
DumpWindows();
@ -1125,96 +1125,27 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
AndroidBridge::AutoLocalJNIFrame jniFrame;
AndroidGeckoSurfaceView& sview(AndroidBridge::Bridge()->SurfaceView());
NS_ASSERTION(!sview.isNull(), "SurfaceView is null!");
if (GetLayerManager(nsnull)->GetBackendType() == LayerManager::LAYERS_BASIC) {
if (sNativeWindow) {
unsigned char *bits;
int width, height, format, stride;
if (!AndroidBridge::Bridge()->LockWindow(sNativeWindow, &bits, &width, &height, &format, &stride)) {
ALOG("failed to lock buffer - skipping draw");
return;
}
AndroidGeckoSoftwareLayerClient &client =
AndroidBridge::Bridge()->GetSoftwareLayerClient();
if (!bits || format != AndroidBridge::WINDOW_FORMAT_RGB_565 ||
width != mBounds.width || height != mBounds.height) {
client.BeginDrawing();
unsigned char *bits = client.LockBufferBits();
ALOG("surface is not expected dimensions or format - skipping draw");
AndroidBridge::Bridge()->UnlockWindow(sNativeWindow);
return;
}
nsRefPtr<gfxImageSurface> targetSurface =
new gfxImageSurface(bits,
gfxIntSize(mBounds.width, mBounds.height),
stride * 2,
gfxASurface::ImageFormatRGB16_565);
if (targetSurface->CairoStatus()) {
ALOG("### Failed to create a valid surface from the bitmap");
} else {
DrawTo(targetSurface);
}
AndroidBridge::Bridge()->UnlockWindow(sNativeWindow);
} else if (AndroidBridge::Bridge()->HasNativeBitmapAccess()) {
jobject bitmap = sview.GetSoftwareDrawBitmap();
if (!bitmap) {
ALOG("no bitmap to draw into - skipping draw");
return;
}
if (!AndroidBridge::Bridge()->ValidateBitmap(bitmap, mBounds.width, mBounds.height))
return;
void *buf = AndroidBridge::Bridge()->LockBitmap(bitmap);
if (buf == nsnull) {
ALOG("### Software drawing, but failed to lock bitmap.");
return;
}
nsRefPtr<gfxImageSurface> targetSurface =
new gfxImageSurface((unsigned char *)buf,
gfxIntSize(mBounds.width, mBounds.height),
mBounds.width * 2,
gfxASurface::ImageFormatRGB16_565);
if (targetSurface->CairoStatus()) {
ALOG("### Failed to create a valid surface from the bitmap");
} else {
DrawTo(targetSurface);
}
AndroidBridge::Bridge()->UnlockBitmap(bitmap);
sview.Draw2D(bitmap, mBounds.width, mBounds.height);
nsRefPtr<gfxImageSurface> targetSurface =
new gfxImageSurface(bits, gfxIntSize(TILE_WIDTH, TILE_HEIGHT), TILE_WIDTH * 2,
gfxASurface::ImageFormatRGB16_565);
if (targetSurface->CairoStatus()) {
ALOG("### Failed to create a valid surface from the bitmap");
} else {
jobject bytebuf = sview.GetSoftwareDrawBuffer();
if (!bytebuf) {
ALOG("no buffer to draw into - skipping draw");
return;
}
void *buf = AndroidBridge::JNI()->GetDirectBufferAddress(bytebuf);
int cap = AndroidBridge::JNI()->GetDirectBufferCapacity(bytebuf);
if (!buf || cap != (mBounds.width * mBounds.height * 2)) {
ALOG("### Software drawing, but unexpected buffer size %d expected %d (or no buffer %p)!", cap, mBounds.width * mBounds.height * 2, buf);
return;
}
nsRefPtr<gfxImageSurface> targetSurface =
new gfxImageSurface((unsigned char *)buf,
gfxIntSize(mBounds.width, mBounds.height),
mBounds.width * 2,
gfxASurface::ImageFormatRGB16_565);
if (targetSurface->CairoStatus()) {
ALOG("### Failed to create a valid surface");
} else {
DrawTo(targetSurface);
}
sview.Draw2D(bytebuf, mBounds.width * 2);
DrawTo(targetSurface, ae->Rect());
}
client.UnlockBuffer();
client.EndDrawing(ae->Rect());
} else {
ALOG("### GL layers are disabled for now in the native UI Fennec");
#if 0
int drawType = sview.BeginDrawing();
if (drawType == AndroidGeckoSurfaceView::DRAW_DISABLED) {
@ -1234,9 +1165,10 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
NS_ASSERTION(sGLContext, "Drawing with GLES without a GL context?");
DrawTo(nsnull);
DrawTo(nsnull, ae->P0(), ae->Alpha());
sview.EndDrawing();
#endif
}
}

View File

@ -178,7 +178,7 @@ public:
protected:
void BringToFront();
nsWindow *FindTopLevel();
bool DrawTo(gfxASurface *targetSurface);
bool DrawTo(gfxASurface *targetSurface, const nsIntRect &aRect);
bool DrawToFile(const nsAString &path);
bool IsTopLevel();
void OnIMEAddRange(mozilla::AndroidGeckoEvent *ae);
@ -220,6 +220,7 @@ private:
void DispatchGestureEvent(PRUint32 msg, PRUint32 direction, double delta,
const nsIntPoint &refPoint, PRUint64 time);
void HandleSpecialKey(mozilla::AndroidGeckoEvent *ae);
void RedrawAll();
#ifdef ACCESSIBILITY
nsRefPtr<nsAccessible> mRootAccessible;