summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
authorTomaž Vajngerl <tomaz.vajngerl@collabora.com>2014-06-26 11:22:21 +0200
committerJan Holesovsky <kendy@collabora.com>2014-06-30 14:48:03 +0200
commita6ecd8b2a9f0f3eaa66388861a6dcc6260ec72b9 (patch)
tree8e2dedeb6bdb4de787962262c38d7af43c6a6b62 /android
parent18d2afbf9a544100c2decd99bee0eb5cf3e8f0e3 (diff)
LOAndroid3: ant/make for building, Bootstrap project
LOAndroid3 is based of LibreOffice4Android project which uses ant/make for building. By using LibreOffice4Android as the base, the project creates a APK archive which has all needed files to start LibreOffice in Android environment. Change-Id: I697d5f727bdaf93e774144ad597d7081d2609908
Diffstat (limited to 'android')
-rw-r--r--android/CustomTarget_lo_android.mk36
-rw-r--r--android/Module_android.mk1
-rw-r--r--android/experimental/LOAndroid3/AndroidManifest.xml29
-rw-r--r--android/experimental/LOAndroid3/Makefile128
-rw-r--r--android/experimental/LOAndroid3/build.xml84
-rw-r--r--android/experimental/LOAndroid3/dummies.cxx75
-rw-r--r--android/experimental/LOAndroid3/fonts.conf154
-rw-r--r--android/experimental/LOAndroid3/jni/Android.mk8
-rw-r--r--android/experimental/LOAndroid3/proguard-project.txt20
-rw-r--r--android/experimental/LOAndroid3/project.properties14
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/base.pngbin0 -> 20684 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/calc.pngbin0 -> 20697 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/draw.pngbin0 -> 18445 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/dummy_page.pngbin0 -> 77517 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/ic_launcher.pngbin0 -> 9397 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/ic_status_logo.pngbin0 -> 1864 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/impress.pngbin0 -> 13936 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/lo_icon.pngbin0 -> 4475 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/main.pngbin0 -> 4380 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/math.pngbin0 -> 15370 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-hdpi/startcenter.pngbin0 -> 4380 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/background.pngbin0 -> 18308 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/base.pngbin0 -> 20684 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/calc.pngbin0 -> 20697 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/docu.pngbin0 -> 203346 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/draw.pngbin0 -> 18445 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/ic_launcher.pngbin0 -> 5237 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/ic_status_logo.pngbin0 -> 1533 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/impress.pngbin0 -> 13936 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/lo_icon.pngbin0 -> 3255 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/shadow.pngbin0 -> 881 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-mdpi/writer.pngbin0 -> 19535 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-xhdpi/base.pngbin0 -> 20684 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-xhdpi/calc.pngbin0 -> 20697 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-xhdpi/draw.pngbin0 -> 18445 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-xhdpi/ic_launcher.pngbin0 -> 14383 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-xhdpi/ic_status_logo.pngbin0 -> 2049 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-xhdpi/impress.pngbin0 -> 13936 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-xhdpi/writer.pngbin0 -> 19535 bytes
-rw-r--r--android/experimental/LOAndroid3/res/drawable-xxhdpi/ic_launcher.pngbin0 -> 19388 bytes
-rw-r--r--android/experimental/LOAndroid3/res/layout/activity_main.xml15
-rw-r--r--android/experimental/LOAndroid3/res/menu/main.xml8
-rw-r--r--android/experimental/LOAndroid3/res/values/colors.xml95
-rw-r--r--android/experimental/LOAndroid3/res/values/dimens.xml5
-rw-r--r--android/experimental/LOAndroid3/res/values/strings.xml7
-rw-r--r--android/experimental/LOAndroid3/res/values/styles.xml8
-rw-r--r--android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java67
-rw-r--r--android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitShell.java131
-rw-r--r--android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java135
-rw-r--r--android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java145
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/GeckoEventListener.java44
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/BufferedCairoImage.java105
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoGLInfo.java72
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoImage.java58
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoUtils.java85
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CheckerboardImage.java170
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/FlexibleGLSurfaceView.java218
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/FloatSize.java99
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLController.java279
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLThread.java181
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoGLLayerClient.java265
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java414
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java322
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/InputConnectionHandler.java15
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/IntSize.java103
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java242
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerClient.java81
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java534
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java689
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java230
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java300
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java124
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PanningPerfAPI.java125
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PointUtils.java96
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/RectUtils.java139
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java425
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java127
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextLayer.java117
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextureGenerator.java73
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextureReaper.java76
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TileLayer.java256
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewTransform.java51
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java306
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/VirtualLayer.java80
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/WidgetTileLayer.java160
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java271
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java921
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SimpleScaleGestureDetector.java332
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java140
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/util/FloatUtils.java43
90 files changed, 9533 insertions, 0 deletions
diff --git a/android/CustomTarget_lo_android.mk b/android/CustomTarget_lo_android.mk
new file mode 100644
index 000000000000..4d32cac168dd
--- /dev/null
+++ b/android/CustomTarget_lo_android.mk
@@ -0,0 +1,36 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_CustomTarget_CustomTarget,android/loandroid3))
+
+loandroid3_DIR := $(call gb_CustomTarget_get_workdir,android/experimental/LOAndroid3)
+
+$(call gb_CustomTarget_get_target,android/loandroid3) : \
+ $(loandroid3_DIR)/done
+
+# We want that to be built completely first,
+# so that we can serialize Ant access to Bootstrap, which is used both
+# by DocumentLoader. We don't want one Ant to be cleaning
+# out Bootstrap while another is building stuff that depends on it.
+# Yeah, this sucks
+
+$(loandroid3_DIR)/done : $(call gb_Postprocess_get_target,AllModulesButInstsetNative)
+ $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),MAK,2)
+ cd $(SRCDIR)/android/experimental/LOAndroid3 && $(MAKE) all
+# Copy to $(BUILDDIR)/instsetoo_native as that is where the tinderbox build script
+# still looks for the .apk, and we want fresh daily builds to be uploaded. Even if
+# the apps as such are mostly useless.
+# Us "foo" instead of the old INPATH
+ mkdir -p $(BUILDDIR)/instsetoo_native/foo/bin; \
+ cp $(SRCDIR)/android/experimental/LOAndroid3/bin/*-debug.apk $(BUILDDIR)/instsetoo_native/foo/bin
+
+$(call gb_CustomTarget_get_clean_target,android/loandroid3) :
+ $(call gb_Output_announce,$(subst $(WORKDIR)/Clean/,,$@),$(false),MAK,2)
+ cd $(SRCDIR)/android/experimental/LOAndroid3 && $(MAKE) clean
+
+# vim: set noet sw=4 ts=4:
diff --git a/android/Module_android.mk b/android/Module_android.mk
index e8604491d6f0..eedc57aa15ba 100644
--- a/android/Module_android.mk
+++ b/android/Module_android.mk
@@ -13,6 +13,7 @@ ifeq ($(OS),ANDROID)
$(eval $(call gb_Module_add_targets,android,\
CustomTarget_lo4android \
+ CustomTarget_lo_android \
CustomTarget_android_desktop \
))
diff --git a/android/experimental/LOAndroid3/AndroidManifest.xml b/android/experimental/LOAndroid3/AndroidManifest.xml
new file mode 100644
index 000000000000..81e88226f0c5
--- /dev/null
+++ b/android/experimental/LOAndroid3/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.libreoffice"
+ android:installLocation="preferExternal"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <!-- App requires OpenGL ES 2.0 -->
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="11"/>
+
+ <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/main"
+ android:label="@string/app_name"
+ android:hardwareAccelerated="true"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name="LibreOfficeMainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/android/experimental/LOAndroid3/Makefile b/android/experimental/LOAndroid3/Makefile
new file mode 100644
index 000000000000..4db011f13616
--- /dev/null
+++ b/android/experimental/LOAndroid3/Makefile
@@ -0,0 +1,128 @@
+ifeq ($(BUILDDIR),)
+include ../../../config_host.mk
+endif
+
+# The default target just builds.
+
+all: build-ant
+
+# The package of this app
+APP_PACKAGE=org.libreoffice
+
+BOOTSTRAPDIR=../../Bootstrap
+include $(BOOTSTRAPDIR)/Makefile.shared
+
+native-code.cxx: $(SRCDIR)/solenv/bin/native-code.py
+ $< -g core -g writer > $@
+
+copy-stuff:
+# Then "assets". Let the directory structure under assets mimic
+# that under solver for now.
+#
+# Please note that I have no idea what all of this is really necessary and for
+# much of this stuff being copied, no idea whether it makes any sense at all.
+# Much of this is copy-pasted from android/qa/sc/Makefile (where a couple of
+# unit tests for sc are built, and those do seem to mostly work) and
+# android/qa/desktop/Makefile (mmeeks's desktop demo, also works to some
+# extent).
+#
+ mkdir -p assets/gz.unpack/program/ure assets/lib assets/program/services assets/ure/share/misc assets/ComponentTarget/i18npool/util
+ gzip -9 <$(INSTDIR)/$(LIBO_ETC_FOLDER)/types/offapi.rdb >assets/gz.unpack/program/offapi.rdb
+ gzip -9 <$(INSTDIR)/$(LIBO_ETC_FOLDER)/types/oovbaapi.rdb >assets/gz.unpack/program/oovbaapi.rdb
+ gzip -9 <$(INSTDIR)/$(LIBO_URE_SHARE_FOLDER)/misc/types.rdb >assets/gz.unpack/program/udkapi.rdb
+# For some reason the vnd.sun.star.expand:$LO_LIB_DIR doesn't seem to work, it expands to empty!?
+ for F in program/services/services ure/share/misc/services; do \
+ sed -e 's!uri="vnd.sun.star.expand:$$LO_LIB_DIR/!uri="file://$$APP_DATA_DIR/lib/!g' <$(INSTDIR)/$$F.rdb >assets/$$F.rdb; \
+ done
+ cp $(SRC_ROOT)/odk/examples/java/DocumentHandling/test/test1.odt \
+ assets
+ cp $(WORKDIR)/ComponentTarget/i18npool/util/i18npool.component assets/ComponentTarget/i18npool/util
+#
+ mkdir -p assets/share/config
+ cp -R $(INSTDIR)/share/registry assets/share
+ cp -R $(INSTDIR)/share/config/soffice.cfg assets/share/config
+#
+# Set up rc, the "inifile". See BootstrapMap::getBaseIni(). As this app
+# doesn't use soffice_main() (at least I think it shouldn't), the
+# rtl::Bootstrap::setIniFilename() call there that hardcodes
+# /assets/program/lofficerc isn't executed. Instead the hardcoding of
+# /assets/rc in BootstrapMap::getBaseIni() gets used.
+ echo '[Bootstrap]' > assets/rc
+ echo 'Logo=1' >> assets/rc
+ echo 'NativeProgress=1' >> assets/rc
+ echo 'URE_BOOTSTRAP=file:///assets/program/fundamentalrc' >> assets/rc
+# echo 'RTL_LOGFILE=file:///dev/log/main' >> assets/rc
+ echo 'HOME=$$APP_DATA_DIR/cache' >> assets/rc
+ echo 'OSL_SOCKET_PATH=$$APP_DATA_DIR/cache' >> assets/rc
+#
+# Set up fundamentalrc
+ echo '[Bootstrap]' > assets/program/fundamentalrc
+ echo 'LO_LIB_DIR=file://$$APP_DATA_DIR/lib/' >> assets/program/fundamentalrc
+ echo 'URE_LIB_DIR=file://$$APP_DATA_DIR/lib/' >> assets/program/fundamentalrc # checkme - is this used to find configs ?
+ echo 'BRAND_BASE_DIR=file:///assets' >> assets/program/fundamentalrc
+ echo 'CONFIGURATION_LAYERS=xcsxcu:$${BRAND_BASE_DIR}/share/registry res:$${BRAND_BASE_DIR}/share/registry' >> assets/program/fundamentalrc
+ echo 'URE_BIN_DIR=file:///assets/ure/bin/dir/nothing-here/we-can/exec-anyway' >> assets/program/fundamentalrc
+#
+# Set up unorc
+ echo '[Bootstrap]' > assets/program/unorc
+ echo 'URE_INTERNAL_LIB_DIR=file://$$APP_DATA_DIR/lib/' >> assets/program/unorc
+ echo 'UNO_TYPES=file://$$APP_DATA_DIR/program/udkapi.rdb file://$$APP_DATA_DIR/program/offapi.rdb file://$$APP_DATA_DIR/program/oovbaapi.rdb' >> assets/program/unorc
+ echo 'UNO_SERVICES=file:///assets/ure/share/misc/services.rdb file:///assets/program/services/services.rdb' >> assets/program/unorc
+#
+# Set up bootstraprc
+ echo '[Bootstrap]' > assets/program/bootstraprc
+ echo 'InstallMode=<installmode>' >> assets/program/bootstraprc
+ echo 'ProductKey=LibreOffice $(LIBO_VERSION_MAJOR).$(LIBO_VERSION_MINOR)' >> assets/program/bootstraprc
+ echo 'UserInstallation=file://$$APP_DATA_DIR' >> assets/program/bootstraprc
+#
+# Set up versionrc
+ echo '[Version]' > assets/program/versionrc
+ echo 'AllLanguages=en-US' >> assets/program/versionrc
+ echo 'BuildVersion=' >> assets/program/versionrc
+ echo 'buildid=dead-beef' >> assets/program/versionrc
+ echo 'ProductMajor=360' >> assets/program/versionrc
+ echo 'ProductMinor=1' >> assets/program/versionrc
+ echo 'ReferenceOOoMajorMinor=3.6' >> assets/program/versionrc
+#
+# .res files
+ mkdir -p assets/program/resource
+ cp $(INSTDIR)/$(LIBO_SHARE_RESOURCE_FOLDER)/*en-US.res assets/program/resource
+#
+# Assets that are unpacked at run-time into the app's data directory. These
+# are files read by non-LO code, fontconfig and freetype for now, that doesn't
+# understand "/assets" paths.
+ mkdir -p assets/unpack/etc/fonts
+ cp fonts.conf assets/unpack/etc/fonts
+# $UserInstallation/user/fonts is added to the fontconfig path in
+# vcl/generic/fontmanager/helper.cxx: psp::getFontPath(). UserInstallation is
+# set to the app's data dir above.
+ mkdir -p assets/gz.unpack/user/fonts
+ for F in $(INSTDIR)/share/fonts/truetype/Liberation*.ttf $(INSTDIR)/share/fonts/truetype/Gen*.ttf $(INSTDIR)/share/fonts/truetype/opens___.ttf; do \
+ gzip -9 <$$F >assets/gz.unpack/user/fonts/`basename $$F`; \
+ done
+#
+# Then gdbserver and gdb.setup so that we can debug with ndk-gdb.
+#
+ mkdir -p $(SODEST)
+ cp $(ANDROID_NDK_GDBSERVER) $(SODEST)
+ echo set solib-search-path ./obj/local/$(ANDROID_APP_ABI) >$(SODEST)/gdb.setup
+
+build-ant: android_version_setup copy-stuff link-so properties
+#
+# Copy jar files we need
+#
+ for F in java_uno \
+ juh \
+ jurt \
+ ridl \
+ unoloader; do \
+ $(call COPYJAR,$(INSTDIR)/$(LIBO_URE_SHARE_JAVA_FOLDER)/$${F}.jar); \
+ done
+ for F in unoil; do \
+ $(call COPYJAR,$(INSTDIR)/$(LIBO_SHARE_JAVA_FOLDER)/$${F}.jar); \
+ done
+#
+ unset JAVA_HOME && $(ANT) -quiet debug
+
+run:
+ adb shell am start -n $(APP_PACKAGE)/.ui.LibreOfficeUIActivity -e input /assets/test1.odt
diff --git a/android/experimental/LOAndroid3/build.xml b/android/experimental/LOAndroid3/build.xml
new file mode 100644
index 000000000000..52d06eede74c
--- /dev/null
+++ b/android/experimental/LOAndroid3/build.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="LibreOfficeViewer" default="help">
+
+ <!-- The local.properties file is created and updated by the 'android' tool.
+ It contains the path to the SDK. It should *NOT* be checked into
+ Version Control Systems. -->
+ <loadproperties srcFile="local.properties" />
+
+ <!-- The ant.properties file can be created by you. It is only edited by the
+ 'android' tool to add properties to it.
+ This is the place to change some Ant specific build properties.
+ Here are some properties you may want to change/update:
+
+ source.dir
+ The name of the source directory. Default is 'src'.
+ out.dir
+ The name of the output directory. Default is 'bin'.
+
+ For other overridable properties, look at the beginning of the rules
+ files in the SDK, at tools/ant/build.xml
+
+ Properties related to the SDK location or the project target should
+ be updated using the 'android' tool with the 'update' action.
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems.
+
+ -->
+ <property file="ant.properties" />
+
+ <!-- The project.properties file is created and updated by the 'android'
+ tool, as well as ADT.
+
+ This contains project specific properties such as project target, and library
+ dependencies. Lower level build properties are stored in ant.properties
+ (or in .classpath for Eclipse projects).
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems. -->
+ <loadproperties srcFile="project.properties" />
+
+ <!-- quick check on sdk.dir -->
+ <fail
+ message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'"
+ unless="sdk.dir"
+ />
+
+
+<!-- extension targets. Uncomment the ones where you want to do custom work
+ in between standard targets -->
+<!--
+ <target name="-pre-build">
+ </target>
+ <target name="-pre-compile">
+ </target>
+
+ /* This is typically used for code obfuscation.
+ Compiled code location: ${out.classes.absolute.dir}
+ If this is not done in place, override ${out.dex.input.absolute.dir} */
+ <target name="-post-compile">
+ </target>
+-->
+
+ <!-- Import the actual build file.
+
+ To customize existing targets, there are two options:
+ - Customize only one target:
+ - copy/paste the target into this file, *before* the
+ <import> task.
+ - customize it to your needs.
+ - Customize the whole content of build.xml
+ - copy/paste the content of the rules files (minus the top node)
+ into this file, replacing the <import> task.
+ - customize to your needs.
+
+ ***********************
+ ****** IMPORTANT ******
+ ***********************
+ In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+ in order to avoid having your file be overridden by tools such as "android update project"
+ -->
+ <!-- version-tag: 1 -->
+ <import file="${android.library.reference.1}/no-resource-compress.xml" />
+</project>
diff --git a/android/experimental/LOAndroid3/dummies.cxx b/android/experimental/LOAndroid3/dummies.cxx
new file mode 100644
index 000000000000..5607ecfd72d6
--- /dev/null
+++ b/android/experimental/LOAndroid3/dummies.cxx
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// Dummy implementations of the callback functions in the UI layer
+// that the LO layer calls. As this experimental Android app doesn't
+// handle any of that, these do nothing.
+
+#include <android/log.h>
+
+#include <touch/touch.h>
+
+extern "C"
+__attribute__ ((visibility("default")))
+void
+touch_ui_selection_start(MLOSelectionKind kind,
+ const void *documentHandle,
+ MLORect *rectangles,
+ int rectangleCount,
+ void *preview)
+{
+}
+
+extern "C"
+__attribute__ ((visibility("default")))
+void
+touch_ui_selection_resize_done(bool success,
+ const void *documentHandle,
+ MLORect *rectangles,
+ int rectangleCount)
+{
+}
+
+extern "C"
+__attribute__ ((visibility("default")))
+void
+touch_ui_selection_none()
+{
+}
+
+
+static const char *
+dialog_kind_to_string(MLODialogKind kind)
+{
+ switch (kind) {
+ case MLODialogMessage:
+ return "MSG";
+ case MLODialogInformation:
+ return "INF";
+ case MLODialogWarning:
+ return "WRN";
+ case MLODialogError:
+ return "ERR";
+ case MLODialogQuery:
+ return "QRY";
+ default:
+ return "WTF";
+ }
+}
+
+extern "C"
+__attribute__ ((visibility("default")))
+MLODialogResult
+touch_ui_dialog_modal(MLODialogKind kind, const char *message)
+{
+ __android_log_print(ANDROID_LOG_INFO, "===> %s: %s", dialog_kind_to_string(kind), message);
+ return MLODialogOK;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/android/experimental/LOAndroid3/fonts.conf b/android/experimental/LOAndroid3/fonts.conf
new file mode 100644
index 000000000000..263648a0aa87
--- /dev/null
+++ b/android/experimental/LOAndroid3/fonts.conf
@@ -0,0 +1,154 @@
+<?xml version="1.0"?>
+<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+<!-- /etc/fonts/fonts.conf file to configure system font access -->
+<fontconfig>
+
+<!-- Font directory list -->
+
+ <dir>/system/fonts</dir>
+
+ <alias>
+ <family>serif</family>
+ <prefer>
+ <family>Droid Serif</family>
+ </prefer>
+ </alias>
+ <alias>
+ <family>sans-serif</family>
+ <prefer>
+ <family>Roboto</family>
+ <family>Droid Sans Fallback</family>
+ </prefer>
+ </alias>
+ <alias>
+ <family>monospace</family>
+ <prefer>
+ <family>Droid Sans Mono</family>
+ </prefer>
+ </alias>
+
+<!--
+ Accept deprecated 'mono' alias, replacing it with 'monospace'
+-->
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>mono</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>monospace</string>
+ </edit>
+ </match>
+
+<!--
+ Accept alternate 'sans serif' spelling, replacing it with 'sans-serif'
+-->
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>sans serif</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>sans-serif</string>
+ </edit>
+ </match>
+
+<!--
+ Accept deprecated 'sans' alias, replacing it with 'sans-serif'
+-->
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>sans</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>sans-serif</string>
+ </edit>
+ </match>
+
+<!--
+ Load local system customization file
+-->
+ <include ignore_missing="yes">conf.d</include>
+
+<!-- Font cache directory list -->
+
+ <!-- Yeah this hardcoding is wrong of course, will have to fix
+ later to patch in proper code in fontonfig on Android to
+ find out a good place.
+ -->
+ <cachedir>/data/data/org.libreoffice/fontconfig</cachedir>
+
+ <config>
+<!--
+ These are the default Unicode chars that are expected to be blank
+ in fonts. All other blank chars are assumed to be broken and
+ won't appear in the resulting charsets
+ -->
+ <blank>
+ <int>0x0020</int> <!-- SPACE -->
+ <int>0x00A0</int> <!-- NO-BREAK SPACE -->
+ <int>0x00AD</int> <!-- SOFT HYPHEN -->
+ <int>0x034F</int> <!-- COMBINING GRAPHEME JOINER -->
+ <int>0x0600</int> <!-- ARABIC NUMBER SIGN -->
+ <int>0x0601</int> <!-- ARABIC SIGN SANAH -->
+ <int>0x0602</int> <!-- ARABIC FOOTNOTE MARKER -->
+ <int>0x0603</int> <!-- ARABIC SIGN SAFHA -->
+ <int>0x06DD</int> <!-- ARABIC END OF AYAH -->
+ <int>0x070F</int> <!-- SYRIAC ABBREVIATION MARK -->
+ <int>0x115F</int> <!-- HANGUL CHOSEONG FILLER -->
+ <int>0x1160</int> <!-- HANGUL JUNGSEONG FILLER -->
+ <int>0x1680</int> <!-- OGHAM SPACE MARK -->
+ <int>0x17B4</int> <!-- KHMER VOWEL INHERENT AQ -->
+ <int>0x17B5</int> <!-- KHMER VOWEL INHERENT AA -->
+ <int>0x180E</int> <!-- MONGOLIAN VOWEL SEPARATOR -->
+ <int>0x2000</int> <!-- EN QUAD -->
+ <int>0x2001</int> <!-- EM QUAD -->
+ <int>0x2002</int> <!-- EN SPACE -->
+ <int>0x2003</int> <!-- EM SPACE -->
+ <int>0x2004</int> <!-- THREE-PER-EM SPACE -->
+ <int>0x2005</int> <!-- FOUR-PER-EM SPACE -->
+ <int>0x2006</int> <!-- SIX-PER-EM SPACE -->
+ <int>0x2007</int> <!-- FIGURE SPACE -->
+ <int>0x2008</int> <!-- PUNCTUATION SPACE -->
+ <int>0x2009</int> <!-- THIN SPACE -->
+ <int>0x200A</int> <!-- HAIR SPACE -->
+ <int>0x200B</int> <!-- ZERO WIDTH SPACE -->
+ <int>0x200C</int> <!-- ZERO WIDTH NON-JOINER -->
+ <int>0x200D</int> <!-- ZERO WIDTH JOINER -->
+ <int>0x200E</int> <!-- LEFT-TO-RIGHT MARK -->
+ <int>0x200F</int> <!-- RIGHT-TO-LEFT MARK -->
+ <int>0x2028</int> <!-- LINE SEPARATOR -->
+ <int>0x2029</int> <!-- PARAGRAPH SEPARATOR -->
+ <int>0x202A</int> <!-- LEFT-TO-RIGHT EMBEDDING -->
+ <int>0x202B</int> <!-- RIGHT-TO-LEFT EMBEDDING -->
+ <int>0x202C</int> <!-- POP DIRECTIONAL FORMATTING -->
+ <int>0x202D</int> <!-- LEFT-TO-RIGHT OVERRIDE -->
+ <int>0x202E</int> <!-- RIGHT-TO-LEFT OVERRIDE -->
+ <int>0x202F</int> <!-- NARROW NO-BREAK SPACE -->
+ <int>0x205F</int> <!-- MEDIUM MATHEMATICAL SPACE -->
+ <int>0x2060</int> <!-- WORD JOINER -->
+ <int>0x2061</int> <!-- FUNCTION APPLICATION -->
+ <int>0x2062</int> <!-- INVISIBLE TIMES -->
+ <int>0x2063</int> <!-- INVISIBLE SEPARATOR -->
+ <int>0x206A</int> <!-- INHIBIT SYMMETRIC SWAPPING -->
+ <int>0x206B</int> <!-- ACTIVATE SYMMETRIC SWAPPING -->
+ <int>0x206C</int> <!-- INHIBIT ARABIC FORM SHAPING -->
+ <int>0x206D</int> <!-- ACTIVATE ARABIC FORM SHAPING -->
+ <int>0x206E</int> <!-- NATIONAL DIGIT SHAPES -->
+ <int>0x206F</int> <!-- NOMINAL DIGIT SHAPES -->
+ <int>0x2800</int> <!-- BRAILLE PATTERN BLANK -->
+ <int>0x3000</int> <!-- IDEOGRAPHIC SPACE -->
+ <int>0x3164</int> <!-- HANGUL FILLER -->
+ <int>0xFEFF</int> <!-- ZERO WIDTH NO-BREAK SPACE -->
+ <int>0xFFA0</int> <!-- HALFWIDTH HANGUL FILLER -->
+ <int>0xFFF9</int> <!-- INTERLINEAR ANNOTATION ANCHOR -->
+ <int>0xFFFA</int> <!-- INTERLINEAR ANNOTATION SEPARATOR -->
+ <int>0xFFFB</int> <!-- INTERLINEAR ANNOTATION TERMINATOR -->
+ </blank>
+<!--
+ Rescan configuration every 3600 seconds when FcFontSetList is called
+ -->
+ <rescan>
+ <int>3600</int>
+ </rescan>
+ </config>
+
+</fontconfig>
diff --git a/android/experimental/LOAndroid3/jni/Android.mk b/android/experimental/LOAndroid3/jni/Android.mk
new file mode 100644
index 000000000000..939a1ea503bb
--- /dev/null
+++ b/android/experimental/LOAndroid3/jni/Android.mk
@@ -0,0 +1,8 @@
+# Needed just to satisfy ndk-gdb for now, but maybe later we will actually add
+# some JNI code here
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/android/experimental/LOAndroid3/proguard-project.txt b/android/experimental/LOAndroid3/proguard-project.txt
new file mode 100644
index 000000000000..f2fe1559a217
--- /dev/null
+++ b/android/experimental/LOAndroid3/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/android/experimental/LOAndroid3/project.properties b/android/experimental/LOAndroid3/project.properties
new file mode 100644
index 000000000000..772d3c57623c
--- /dev/null
+++ b/android/experimental/LOAndroid3/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-15
+
+# Use the Bootstrap class
+android.library.reference.1=../../Bootstrap
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/base.png b/android/experimental/LOAndroid3/res/drawable-hdpi/base.png
new file mode 100644
index 000000000000..729dbcd82ebf
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/base.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/calc.png b/android/experimental/LOAndroid3/res/drawable-hdpi/calc.png
new file mode 100644
index 000000000000..a3f5fd4d80c0
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/calc.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/draw.png b/android/experimental/LOAndroid3/res/drawable-hdpi/draw.png
new file mode 100644
index 000000000000..b3ee11426a04
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/draw.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/dummy_page.png b/android/experimental/LOAndroid3/res/drawable-hdpi/dummy_page.png
new file mode 100644
index 000000000000..c58d276e7085
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/dummy_page.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/ic_launcher.png b/android/experimental/LOAndroid3/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000000..96a442e5b8e9
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/ic_status_logo.png b/android/experimental/LOAndroid3/res/drawable-hdpi/ic_status_logo.png
new file mode 100644
index 000000000000..d5f16694f342
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/ic_status_logo.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/impress.png b/android/experimental/LOAndroid3/res/drawable-hdpi/impress.png
new file mode 100644
index 000000000000..5909f05bf089
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/impress.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/lo_icon.png b/android/experimental/LOAndroid3/res/drawable-hdpi/lo_icon.png
new file mode 100644
index 000000000000..2ef86417e69e
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/lo_icon.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/main.png b/android/experimental/LOAndroid3/res/drawable-hdpi/main.png
new file mode 100644
index 000000000000..7e8e2a05e2da
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/main.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/math.png b/android/experimental/LOAndroid3/res/drawable-hdpi/math.png
new file mode 100644
index 000000000000..50b8dc863bff
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/math.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-hdpi/startcenter.png b/android/experimental/LOAndroid3/res/drawable-hdpi/startcenter.png
new file mode 100644
index 000000000000..7e8e2a05e2da
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-hdpi/startcenter.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/background.png b/android/experimental/LOAndroid3/res/drawable-mdpi/background.png
new file mode 100644
index 000000000000..611592b5167a
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/background.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/base.png b/android/experimental/LOAndroid3/res/drawable-mdpi/base.png
new file mode 100644
index 000000000000..729dbcd82ebf
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/base.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/calc.png b/android/experimental/LOAndroid3/res/drawable-mdpi/calc.png
new file mode 100644
index 000000000000..a3f5fd4d80c0
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/calc.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/docu.png b/android/experimental/LOAndroid3/res/drawable-mdpi/docu.png
new file mode 100644
index 000000000000..ab34ae5638e1
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/docu.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/draw.png b/android/experimental/LOAndroid3/res/drawable-mdpi/draw.png
new file mode 100644
index 000000000000..b3ee11426a04
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/draw.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/ic_launcher.png b/android/experimental/LOAndroid3/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000000..359047dfa4ed
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/ic_status_logo.png b/android/experimental/LOAndroid3/res/drawable-mdpi/ic_status_logo.png
new file mode 100644
index 000000000000..835fc9290727
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/ic_status_logo.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/impress.png b/android/experimental/LOAndroid3/res/drawable-mdpi/impress.png
new file mode 100644
index 000000000000..5909f05bf089
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/impress.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/lo_icon.png b/android/experimental/LOAndroid3/res/drawable-mdpi/lo_icon.png
new file mode 100644
index 000000000000..4f3f89beadc2
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/lo_icon.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/shadow.png b/android/experimental/LOAndroid3/res/drawable-mdpi/shadow.png
new file mode 100644
index 000000000000..3ce69155c6b5
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/shadow.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-mdpi/writer.png b/android/experimental/LOAndroid3/res/drawable-mdpi/writer.png
new file mode 100644
index 000000000000..2f4abcb280cd
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-mdpi/writer.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-xhdpi/base.png b/android/experimental/LOAndroid3/res/drawable-xhdpi/base.png
new file mode 100644
index 000000000000..729dbcd82ebf
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-xhdpi/base.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-xhdpi/calc.png b/android/experimental/LOAndroid3/res/drawable-xhdpi/calc.png
new file mode 100644
index 000000000000..a3f5fd4d80c0
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-xhdpi/calc.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-xhdpi/draw.png b/android/experimental/LOAndroid3/res/drawable-xhdpi/draw.png
new file mode 100644
index 000000000000..b3ee11426a04
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-xhdpi/draw.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-xhdpi/ic_launcher.png b/android/experimental/LOAndroid3/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000000..71c6d760f051
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-xhdpi/ic_status_logo.png b/android/experimental/LOAndroid3/res/drawable-xhdpi/ic_status_logo.png
new file mode 100644
index 000000000000..c8005425416a
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-xhdpi/ic_status_logo.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-xhdpi/impress.png b/android/experimental/LOAndroid3/res/drawable-xhdpi/impress.png
new file mode 100644
index 000000000000..5909f05bf089
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-xhdpi/impress.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-xhdpi/writer.png b/android/experimental/LOAndroid3/res/drawable-xhdpi/writer.png
new file mode 100644
index 000000000000..2f4abcb280cd
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-xhdpi/writer.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/drawable-xxhdpi/ic_launcher.png b/android/experimental/LOAndroid3/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000000..4df18946442e
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/android/experimental/LOAndroid3/res/layout/activity_main.xml b/android/experimental/LOAndroid3/res/layout/activity_main.xml
new file mode 100644
index 000000000000..7b53d58a2b5c
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/layout/activity_main.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/main_layout"
+ android:background="#fff"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <RelativeLayout
+ android:id="@+id/gecko_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/android/experimental/LOAndroid3/res/menu/main.xml b/android/experimental/LOAndroid3/res/menu/main.xml
new file mode 100644
index 000000000000..e9709c90d729
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/menu/main.xml
@@ -0,0 +1,8 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="org.libreoffice.MainActivity" >
+ <item android:id="@+id/action_settings"
+ android:title="@string/action_settings"
+ android:orderInCategory="100" />
+</menu>
diff --git a/android/experimental/LOAndroid3/res/values/colors.xml b/android/experimental/LOAndroid3/res/values/colors.xml
new file mode 100644
index 000000000000..f8e207d4e00e
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/values/colors.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<resources>
+ <color name="background_light">#FFECF0F3</color>
+ <color name="background_normal">#FFCED7DE</color>
+ <color name="background_private">#FF292C29</color>
+ <color name="background_tabs">#FF363B40</color>
+ <color name="highlight">#33000000</color>
+ <color name="highlight_focused">#1A000000</color>
+ <color name="highlight_dark">#33FFFFFF</color>
+ <color name="highlight_dark_focused">#1AFFFFFF</color>
+
+ <!-- highlight on shaped button: 20% white over background_tabs -->
+ <color name="highlight_shaped">#FF696D71</color>
+
+ <!-- highlight-focused on shaped button: 10% white over background_tabs -->
+ <color name="highlight_shaped_focused">#FF565B60</color>
+
+ <!-- highlight on nav button: 20% black over background_normal -->
+ <color name="highlight_nav">#FFA5ACB2</color>
+
+ <!-- highlight-focused on nav button: 10% black over background_normal -->
+ <color name="highlight_nav_focused">#FFB9C1C7</color>
+
+ <!-- highlight on private nav button: 20% white over background_private -->
+ <color name="highlight_nav_pb">#FF545654</color>
+
+ <!-- highlight-focused on private nav button: 10% white over background_private -->
+ <color name="highlight_nav_focused_pb">#FF3F423F</color>
+
+ <!--
+ Application theme colors
+ -->
+ <!-- Default colors -->
+ <color name="text_color_primary">#222222</color>
+ <color name="text_color_secondary">#777777</color>
+ <color name="text_color_tertiary">#9198A1</color>
+
+ <!-- Default inverse colors -->
+ <color name="text_color_primary_inverse">#FFFFFF</color>
+ <color name="text_color_secondary_inverse">#DDDDDD</color>
+ <color name="text_color_tertiary_inverse">#A4A7A9</color>
+
+ <!-- Disabled colors -->
+ <color name="text_color_primary_disable_only">#999999</color>
+
+ <!-- Hint colors -->
+ <color name="text_color_hint">#666666</color>
+ <color name="text_color_hint_inverse">#7F828A</color>
+
+ <!-- Highlight colors -->
+ <color name="text_color_highlight">#FF9500</color>
+ <color name="text_color_highlight_inverse">#D06BFF</color>
+
+ <!-- Link colors -->
+ <color name="text_color_link">#22629E</color>
+
+ <color name="splash_background">#000000</color>
+ <color name="splash_msgfont">#ffffff</color>
+ <color name="splash_urlfont">#000000</color>
+ <color name="splash_content">#ffffff</color>
+
+ <color name="doorhanger_text">#FF222222</color>
+ <color name="doorhanger_link">#FF2AA1FE</color>
+ <color name="doorhanger_divider_light">#FFD1D5DA</color>
+ <color name="doorhanger_divider_dark">#FFB3C2CE</color>
+ <color name="doorhanger_background_dark">#FFDDE4EA</color>
+
+ <color name="validation_message_text">#ffffff</color>
+ <color name="url_bar_text_highlight">#FFFF9500</color>
+ <color name="url_bar_text_highlight_pb">#FFD06BFF</color>
+ <color name="suggestion_primary">#dddddd</color>
+ <color name="suggestion_pressed">#bbbbbb</color>
+ <color name="tab_row_pressed">#4D000000</color>
+ <color name="dialogtitle_textcolor">#ffffff</color>
+
+ <color name="textbox_background">#FFF</color>
+ <color name="textbox_background_disabled">#DDD</color>
+ <color name="textbox_stroke">#000</color>
+ <color name="textbox_stroke_disabled">#666</color>
+
+ <color name="url_bar_urltext">#A6A6A6</color>
+ <color name="url_bar_domaintext">#000</color>
+ <color name="url_bar_domaintext_private">#FFF</color>
+ <color name="url_bar_blockedtext">#b14646</color>
+ <color name="url_bar_shadow">#12000000</color>
+
+ <color name="home_last_tab_bar_bg">#FFF5F7F9</color>
+
+ <color name="panel_grid_item_image_background">#D1D9E1</color>
+</resources>
+
diff --git a/android/experimental/LOAndroid3/res/values/dimens.xml b/android/experimental/LOAndroid3/res/values/dimens.xml
new file mode 100644
index 000000000000..47c82246738c
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/android/experimental/LOAndroid3/res/values/strings.xml b/android/experimental/LOAndroid3/res/values/strings.xml
new file mode 100644
index 000000000000..93431ed24788
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">LibreOffice</string>
+ <string name="action_settings">Settings</string>
+
+</resources>
diff --git a/android/experimental/LOAndroid3/res/values/styles.xml b/android/experimental/LOAndroid3/res/values/styles.xml
new file mode 100644
index 000000000000..ff6c9d2c0fb9
--- /dev/null
+++ b/android/experimental/LOAndroid3/res/values/styles.xml
@@ -0,0 +1,8 @@
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ </style>
+
+</resources>
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java
new file mode 100644
index 000000000000..bf4f98b845da
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java
@@ -0,0 +1,67 @@
+package org.libreoffice;
+
+import android.graphics.Rect;
+import android.util.Log;
+
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.ViewportMetrics;
+
+public class LOEvent {
+
+ public static final int SIZE_CHANGED = 1;
+ public static final int TILE_SIZE = 2;
+ public static final int VIEWPORT = 3;
+ public static final int DRAW = 4;
+
+ private ViewportMetrics mViewportMetrics;
+
+ public int mType;
+ private String mTypeString;
+
+ ViewportMetrics viewportMetrics;
+
+ public LOEvent(int type, int width, int height, int widthPixels, int heightPixels, int tileWidth, int tileHeight) {
+ mType = type;
+ mTypeString = "Size Changed";
+ }
+
+ public LOEvent(int type, IntSize tileSize) {
+ mType = type;
+ mTypeString = "Tile size";
+ }
+
+ public LOEvent(int type, ViewportMetrics viewportMetrics) {
+ mType = type;
+ mTypeString = "Viewport";
+ mViewportMetrics = viewportMetrics;
+ }
+
+ public LOEvent(int type, Rect rect) {
+ mType = type;
+ mTypeString = "Draw";
+ }
+
+ public static LOEvent draw(Rect rect) {
+ return new LOEvent(DRAW, rect);
+ }
+
+ public static LOEvent sizeChanged(int width, int height, int widthPixels, int heightPixels, int tileWidth, int tileHeight) {
+ return new LOEvent(SIZE_CHANGED, width, height, widthPixels, heightPixels, tileWidth, tileHeight);
+ }
+
+ public static LOEvent tileSize(IntSize tileSize) {
+ return new LOEvent(TILE_SIZE, tileSize);
+ }
+
+ public static LOEvent viewport(ViewportMetrics viewportMetrics) {
+ return new LOEvent(VIEWPORT, viewportMetrics);
+ }
+
+ public String getTypeString() {
+ return mTypeString;
+ }
+
+ public ViewportMetrics getViewport() {
+ return mViewportMetrics;
+ }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitShell.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitShell.java
new file mode 100644
index 000000000000..9dde7906d020
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitShell.java
@@ -0,0 +1,131 @@
+package org.libreoffice;
+
+
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.LayerView;
+
+import java.nio.ByteBuffer;
+
+public class LOKitShell {
+ private static final String LOGTAG = LOKitShell.class.getSimpleName();
+
+ public static int getDpi() {
+ return 96;
+ }
+
+ public static int getScreenDepth() {
+ return 24;
+ }
+
+ public static float computeRenderIntegrity() {
+ return 0.0f;
+ }
+
+ public static ByteBuffer allocateDirectBuffer(int size) {
+ if (size <= 0) {
+ throw new IllegalArgumentException("Invalid size " + size);
+ }
+
+ ByteBuffer directBuffer = ByteBuffer.allocateDirect(size);
+ //ByteBuffer directBuffer = nativeAllocateDirectBuffer(size);
+ if (directBuffer == null) {
+ throw new OutOfMemoryError("allocateDirectBuffer() returned null");
+ } else if (!directBuffer.isDirect()) {
+ throw new AssertionError("allocateDirectBuffer() did not return a direct buffer");
+ }
+
+ return directBuffer;
+ }
+
+
+ public static void freeDirectBuffer(ByteBuffer buffer) {
+ if (buffer == null) {
+ return;
+ }
+
+ if (!buffer.isDirect()) {
+ throw new IllegalArgumentException("buffer must be direct");
+ }
+ //nativeFreeDirectBuffer(buffer);
+ return ;
+ }
+
+ public static void bindWidgetTexture() {
+ }
+
+ public static void sendEvent(LOEvent event) {
+ Log.i(LOGTAG, "Event: " + event.getTypeString());
+ }
+
+ public static void runGecko(String apkPath, String args, String url, boolean restoreSession) {
+ // run gecko -- it will spawn its own thread
+ // GeckoAppShell.nativeInit();
+
+ Log.i(LOGTAG, "post native init");
+
+ // Tell Gecko where the target byte buffer is for rendering
+ //GeckoAppShell.setSoftwareLayerClient(GeckoApp.mAppContext.getSoftwareLayerClient());
+
+ Log.i(LOGTAG, "setSoftwareLayerClient called");
+
+ // First argument is the .apk path
+ String combinedArgs = apkPath + " -greomni " + apkPath;
+ if (args != null)
+ combinedArgs += " " + args;
+ if (url != null)
+ combinedArgs += " -remote " + url;
+ if (restoreSession)
+ combinedArgs += " -restoresession";
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ LibreOfficeMainActivity.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ combinedArgs += " -width " + metrics.widthPixels + " -height " + metrics.heightPixels;
+
+ LibreOfficeMainActivity.mAppContext.runOnUiThread(new Runnable() {
+ public void run() {
+ geckoLoaded();
+ }
+ });
+
+ //LOKitShell.nativeRun(combinedArgs);
+ }
+
+ // Called on the UI thread after Gecko loads.
+ private static void geckoLoaded() {
+ /*final LayerController layerController = LibreOfficeMainActivity.mAppContext.getLayerController();
+ LayerView v = layerController.getView();
+ mInputConnection = GeckoInputConnection.create(v);
+ v.setInputConnectionHandler(mInputConnection);
+
+ layerController.setOnTouchListener(new View.OnTouchListener() {
+ public boolean onTouch(View view, MotionEvent event) {
+ if (event == null)
+ return true;
+ GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
+ return true;
+ }
+ });
+
+ layerController.notifyLayerClientOfGeometryChange();*/
+ }
+
+ public static void viewSizeChanged() {
+ }
+
+ public static void scheduleComposite() {
+ }
+
+ public static void schedulePauseComposition() {
+ }
+
+ public static void scheduleResumeComposition() {
+
+ }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
new file mode 100644
index 000000000000..ea3472b8c52c
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -0,0 +1,135 @@
+package org.libreoffice;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.JsonWriter;
+
+import org.mozilla.gecko.gfx.ViewportMetrics;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class LOKitThread extends Thread {
+ private static final String LOGTAG = "GeckoThread";
+
+ public ConcurrentLinkedQueue<LOEvent> gEvents = new ConcurrentLinkedQueue<LOEvent>();
+ private ViewportMetrics mViewportMetrics;
+ private Random rand = new Random();
+
+ LOKitThread() {
+ }
+
+ private boolean draw() throws InterruptedException {
+ final LibreOfficeMainActivity application = LibreOfficeMainActivity.mAppContext;
+
+ Bitmap bitmap = application.getLayerClient().getLayerController().getDrawable("docu");
+ //bitmap = convert(bitmap, Bitmap.Config.ARGB_8888);
+
+ StringWriter stringWriter = new StringWriter();
+
+ try {
+ JsonWriter writer = new JsonWriter(stringWriter);
+ writer.beginObject();
+ if (mViewportMetrics == null) {
+ writer.name("x").value(0);
+ writer.name("y").value(0);
+ writer.name("width").value(bitmap.getWidth());
+ writer.name("height").value(bitmap.getHeight());
+ writer.name("pageWidth").value(bitmap.getWidth());
+ writer.name("pageHeight").value(bitmap.getHeight());
+ writer.name("offsetX").value(0);
+ writer.name("offsetY").value(0);
+ writer.name("zoom").value(0.5);
+ } else {
+ writer.name("x").value(mViewportMetrics.getOrigin().x);
+ writer.name("y").value(mViewportMetrics.getOrigin().y);
+ writer.name("width").value(mViewportMetrics.getSize().width);
+ writer.name("height").value(mViewportMetrics.getSize().height);
+ writer.name("pageWidth").value(mViewportMetrics.getPageSize().width);
+ writer.name("pageHeight").value(mViewportMetrics.getPageSize().height);
+ writer.name("offsetX").value(mViewportMetrics.getViewportOffset().x);
+ writer.name("offsetY").value(mViewportMetrics.getViewportOffset().y);
+ writer.name("zoom").value(mViewportMetrics.getZoomFactor());
+ }
+ writer.name("backgroundColor").value("rgb(255,255,255)");
+ writer.endObject();
+ writer.close();
+ } catch (IOException ex) {
+ }
+
+ Rect bufferRect = application.getLayerClient().beginDrawing(bitmap.getWidth(), bitmap.getHeight(), 256, 256, stringWriter.toString(), false);
+
+ if (bufferRect == null) {
+ return false;
+ }
+ ByteBuffer buffer = application.getLayerClient().lockBuffer();
+ bitmap.copyPixelsToBuffer(buffer.asIntBuffer());
+ application.getLayerClient().unlockBuffer();
+
+ application.getLayerClient().endDrawing(0, 0, bitmap.getWidth(), bitmap.getHeight());
+
+ application.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ application.getLayerClient().handleMessage("Viewport:UpdateLater", null);
+ }
+ });
+ return true;
+ }
+
+ private short convertTo16Bit(int color) {
+ int r = Color.red(color) >> 3, g = Color.green(color) >> 2, b = Color.blue(color) >> 3;
+ int c = ((r << 11) | (g << 5) | b);
+ // Swap endianness.
+ return (short) ((c >> 8) | ((c & 0xff) << 8));
+ }
+
+ private Bitmap convert(Bitmap bitmap, Bitmap.Config config) {
+ Bitmap convertedBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), config);
+ Canvas canvas = new Canvas(convertedBitmap);
+ Paint paint = new Paint();
+ paint.setColor(Color.BLACK);
+ canvas.drawBitmap(bitmap, 0, 0, paint);
+ return convertedBitmap;
+ }
+
+
+ public void run() {
+ try {
+ boolean drawn = false;
+ while (true) {
+
+ if (!gEvents.isEmpty()) {
+ processEvent(gEvents.poll());
+ } else {
+ if (!drawn) {
+ drawn = draw();
+ }
+ Thread.sleep(100L);
+ }
+ }
+ } catch (InterruptedException ex) {
+ }
+ }
+
+ private void processEvent(LOEvent event) throws InterruptedException {
+ switch (event.mType) {
+ case LOEvent.VIEWPORT:
+ mViewportMetrics = event.getViewport();
+ break;
+ case LOEvent.DRAW:
+ draw();
+ break;
+ case LOEvent.SIZE_CHANGED:
+ break;
+ }
+ }
+
+
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
new file mode 100644
index 000000000000..d4758443bf13
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -0,0 +1,145 @@
+package org.libreoffice;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.LayerView;
+
+import org.libreoffice.android.Bootstrap;
+
+import com.sun.star.frame.XComponentLoader;
+import com.sun.star.lang.XMultiComponentFactory;
+import com.sun.star.uno.XComponentContext;
+import com.sun.star.uno.UnoRuntime;
+
+public class LibreOfficeMainActivity extends Activity {
+
+ private static final String LOGTAG = "LibreOfficeMainActivity";
+
+ private LinearLayout mMainLayout;
+ private RelativeLayout mGeckoLayout;
+ private static LayerController mLayerController;
+ private static GeckoSoftwareLayerClient mLayerClient;
+ private static LOKitThread sLOKitThread;
+
+ private XComponentContext context;
+ private XMultiComponentFactory mcf;
+ private XComponentLoader componentLoader;
+
+ public static LibreOfficeMainActivity mAppContext;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+ if (id == R.id.action_settings) {
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ public DisplayMetrics getDisplayMetrics() {
+ DisplayMetrics metrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ return metrics;
+ }
+
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mAppContext = this;
+
+ super.onCreate(savedInstanceState);
+
+ try {
+ Bootstrap.setup(this);
+ Bootstrap.putenv("SAL_LOG=+WARN+INFO-INFO.legacy.osl");
+
+ setContentView(R.layout.activity_main);
+
+ Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - onCreate");
+
+ String input = "/assets/test1.odt";
+
+ String[] argv = { "lo-document-loader", input };
+
+ Bootstrap.setCommandArgs(argv);
+
+ Bootstrap.initVCL();
+
+ context = com.sun.star.comp.helper.Bootstrap.defaultBootstrap_InitialComponentContext();
+
+ Log.i(LOGTAG, "context is" + (context!=null ? " not" : "") + " null");
+
+ mcf = context.getServiceManager();
+
+ Log.i(LOGTAG, "mcf is" + (mcf!=null ? " not" : "") + " null");
+
+ Object desktop = mcf.createInstanceWithContext("com.sun.star.frame.Desktop", context);
+ Log.i(LOGTAG, "desktop is" + (desktop!=null ? " not" : "") + " null");
+
+ componentLoader = (XComponentLoader) UnoRuntime.queryInterface(XComponentLoader.class, desktop);
+ Log.i(LOGTAG, "componentLoader is" + (componentLoader!=null ? " not" : "") + " null");
+
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ //finish();
+ }
+
+ setContentView(R.layout.activity_main);
+
+ // setup gecko layout
+ mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
+ mMainLayout = (LinearLayout) findViewById(R.id.main_layout);
+
+
+ if (mLayerController == null) {
+ mLayerController = new LayerController(this);
+
+ Log.e(LOGTAG, "### Creating GeckoSoftwareLayerClient");
+ mLayerClient = new GeckoSoftwareLayerClient(this);
+ Log.e(LOGTAG, "### Done creating GeckoSoftwareLayerClient");
+
+ mLayerController.setLayerClient(mLayerClient);
+ mGeckoLayout.addView(mLayerController.getView(), 0);
+ }
+
+ mLayerController.notifyLayerClientOfGeometryChange();
+
+ sLOKitThread = new LOKitThread();
+ sLOKitThread.start();
+
+
+ Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - UI almost up");
+ }
+
+ public static GeckoSoftwareLayerClient getLayerClient() {
+ return mLayerClient;
+ }
+
+ public static LayerController getLayerController() {
+ return mLayerController;
+ }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/GeckoEventListener.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/GeckoEventListener.java
new file mode 100644
index 000000000000..670513f2cfb2
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/GeckoEventListener.java
@@ -0,0 +1,44 @@
+/* -*- 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):
+ * Sriram Ramasubramanian <sriram@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;
+
+import org.json.JSONObject;
+
+public interface GeckoEventListener {
+ public void handleMessage(String event, JSONObject message);
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/BufferedCairoImage.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/BufferedCairoImage.java
new file mode 100644
index 000000000000..b6c397767619
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/BufferedCairoImage.java
@@ -0,0 +1,105 @@
+/* -*- 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 android.graphics.Bitmap;
+
+import org.libreoffice.LOKitShell;
+
+import java.nio.ByteBuffer;
+
+//import org.mozilla.gecko.GeckoAppShell;
+
+/**
+ * A Cairo image that simply saves a buffer of pixel data.
+ */
+public class BufferedCairoImage extends CairoImage {
+ private ByteBuffer mBuffer;
+ private IntSize mSize;
+ private int mFormat;
+ private boolean mNeedToFreeBuffer = false;
+
+ /**
+ * Creates a buffered Cairo image from a byte buffer.
+ */
+ public BufferedCairoImage(ByteBuffer inBuffer, int inWidth, int inHeight, int inFormat) {
+ mBuffer = inBuffer;
+ mSize = new IntSize(inWidth, inHeight);
+ mFormat = inFormat;
+ }
+
+ /**
+ * Creates a buffered Cairo image from an Android bitmap.
+ */
+ public BufferedCairoImage(Bitmap bitmap) {
+ mFormat = CairoUtils.bitmapConfigToCairoFormat(bitmap.getConfig());
+ mSize = new IntSize(bitmap.getWidth(), bitmap.getHeight());
+ mNeedToFreeBuffer = true;
+ // XXX Why is this * 4? Shouldn't it depend on mFormat?
+ mBuffer = /*GeckoAppShell*/LOKitShell.allocateDirectBuffer(mSize.getArea() * 4);
+
+ bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (mNeedToFreeBuffer && mBuffer != null)
+ /*GeckoAppShell*/ LOKitShell.freeDirectBuffer(mBuffer);
+ mNeedToFreeBuffer = false;
+ mBuffer = null;
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public ByteBuffer getBuffer() {
+ return mBuffer;
+ }
+
+ @Override
+ public IntSize getSize() {
+ return mSize;
+ }
+
+ @Override
+ public int getFormat() {
+ return mFormat;
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoGLInfo.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoGLInfo.java
new file mode 100644
index 000000000000..bd4eedcaf951
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoGLInfo.java
@@ -0,0 +1,72 @@
+/* -*- 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;
+
+/**
+ * Information needed to render Cairo bitmaps using OpenGL ES.
+ */
+public class CairoGLInfo {
+ public final int internalFormat;
+ public final int format;
+ public final int type;
+
+ public CairoGLInfo(int cairoFormat) {
+ switch (cairoFormat) {
+ case CairoImage.FORMAT_ARGB32:
+ internalFormat = format = GL10.GL_RGBA;
+ type = GL10.GL_UNSIGNED_BYTE;
+ break;
+ case CairoImage.FORMAT_RGB24:
+ internalFormat = format = GL10.GL_RGB;
+ type = GL10.GL_UNSIGNED_BYTE;
+ break;
+ case CairoImage.FORMAT_RGB16_565:
+ internalFormat = format = GL10.GL_RGB;
+ type = GL10.GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ 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");
+ }
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoImage.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoImage.java
new file mode 100644
index 000000000000..06c389dd0524
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoImage.java
@@ -0,0 +1,58 @@
+/* -*- 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 getBuffer();
+
+ public abstract IntSize getSize();
+ 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;
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoUtils.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoUtils.java
new file mode 100644
index 000000000000..00bd896e1664
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CairoUtils.java
@@ -0,0 +1,85 @@
+/* -*- 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 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");
+ }
+ }
+
+ public static Bitmap.Config cairoFormatTobitmapConfig(int format) {
+ switch (format) {
+ case CairoImage.FORMAT_A8: return Bitmap.Config.ALPHA_8;
+ case CairoImage.FORMAT_ARGB32: return Bitmap.Config.ARGB_8888;
+ case CairoImage.FORMAT_RGB16_565: return Bitmap.Config.RGB_565;
+ default:
+ throw new RuntimeException("Unknown CairoImage format");
+ }
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CheckerboardImage.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CheckerboardImage.java
new file mode 100644
index 000000000000..392d7e8d8463
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CheckerboardImage.java
@@ -0,0 +1,170 @@
+/* -*- 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) 2012
+ * 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.libreoffice.LOKitShell;
+import android.graphics.Color;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.util.Arrays;
+
+/** A Cairo image that displays a tinted checkerboard. */
+public class CheckerboardImage extends CairoImage {
+ // The width and height of the checkerboard tile.
+ private static final int SIZE = 16;
+ // The pixel format of the checkerboard tile.
+ private static final int FORMAT = CairoImage.FORMAT_RGB16_565;
+ // The color to mix in to tint the background color.
+ private static final int TINT_COLOR = Color.GRAY;
+ // The amount to mix in.
+ private static final float TINT_OPACITY = 0.4f;
+
+ private ByteBuffer mBuffer;
+ private int mMainColor;
+ private boolean mShowChecks;
+
+ /** Creates a new checkerboard image. */
+ public CheckerboardImage() {
+ int bpp = CairoUtils.bitsPerPixelForCairoFormat(FORMAT);
+ mBuffer = LOKitShell.allocateDirectBuffer(SIZE * SIZE * bpp / 8);
+ update(true, Color.WHITE);
+ }
+
+ /** Returns the current color of the checkerboard. */
+ public int getColor() {
+ return mMainColor;
+ }
+
+ /** Returns whether or not we are currently showing checks on the checkerboard. */
+ public boolean getShowChecks() {
+ return mShowChecks;
+ }
+
+ /** Updates the checkerboard image. If showChecks is true, then create a
+ checkerboard image that is tinted to the color. Otherwise just return a flat
+ image of the color. */
+ public void update(boolean showChecks, int color) {
+ mMainColor = color;
+ mShowChecks = showChecks;
+
+ short mainColor16 = convertTo16Bit(mMainColor);
+
+ mBuffer.rewind();
+ ShortBuffer shortBuffer = mBuffer.asShortBuffer();
+
+ if (!mShowChecks) {
+ short color16 = convertTo16Bit(mMainColor);
+ short[] fillBuffer = new short[SIZE];
+ Arrays.fill(fillBuffer, color16);
+
+ for (int i = 0; i < SIZE; i++) {
+ shortBuffer.put(fillBuffer);
+ }
+
+ return;
+ }
+
+ short tintColor16 = convertTo16Bit(tint(mMainColor));
+
+ short[] mainPattern = new short[SIZE / 2], tintPattern = new short[SIZE / 2];
+ Arrays.fill(mainPattern, mainColor16);
+ Arrays.fill(tintPattern, tintColor16);
+
+ // The checkerboard pattern looks like this:
+ //
+ // +---+---+
+ // | N | T | N = normal
+ // +---+---+ T = tinted
+ // | T | N |
+ // +---+---+
+
+ for (int i = 0; i < SIZE / 2; i++) {
+ shortBuffer.put(mainPattern);
+ shortBuffer.put(tintPattern);
+ }
+ for (int i = SIZE / 2; i < SIZE; i++) {
+ shortBuffer.put(tintPattern);
+ shortBuffer.put(mainPattern);
+ }
+ }
+
+ // Tints the given color appropriately and returns the tinted color.
+ private int tint(int color) {
+ float negTintOpacity = 1.0f - TINT_OPACITY;
+ float r = Color.red(color) * negTintOpacity + Color.red(TINT_COLOR) * TINT_OPACITY;
+ float g = Color.green(color) * negTintOpacity + Color.green(TINT_COLOR) * TINT_OPACITY;
+ float b = Color.blue(color) * negTintOpacity + Color.blue(TINT_COLOR) * TINT_OPACITY;
+ return Color.rgb(Math.round(r), Math.round(g), Math.round(b));
+ }
+
+ // Converts a 32-bit ARGB color to 16-bit R5G6B5, truncating values and discarding the alpha
+ // channel.
+ private short convertTo16Bit(int color) {
+ int r = Color.red(color) >> 3, g = Color.green(color) >> 2, b = Color.blue(color) >> 3;
+ int c = ((r << 11) | (g << 5) | b);
+ // Swap endianness.
+ return (short)((c >> 8) | ((c & 0xff) << 8));
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mBuffer != null) {
+ LOKitShell.freeDirectBuffer(mBuffer);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public ByteBuffer getBuffer() {
+ return mBuffer;
+ }
+
+ @Override
+ public IntSize getSize() {
+ return new IntSize(SIZE, SIZE);
+ }
+
+ @Override
+ public int getFormat() {
+ return FORMAT;
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/FlexibleGLSurfaceView.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/FlexibleGLSurfaceView.java
new file mode 100644
index 000000000000..dc20077b0457
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/FlexibleGLSurfaceView.java
@@ -0,0 +1,218 @@
+/* -*- 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) 2011-2012
+ * 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.GeckoApp;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import org.libreoffice.LibreOfficeMainActivity;
+
+public class FlexibleGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+ private static final String LOGTAG = "GeckoFlexibleGLSurfaceView";
+
+ private GLSurfaceView.Renderer mRenderer;
+ private GLThread mGLThread; // Protected by this class's monitor.
+ private GLController mController;
+ private Listener mListener;
+
+ public FlexibleGLSurfaceView(Context context) {
+ super(context);
+ init();
+ }
+
+ public FlexibleGLSurfaceView(Context context, AttributeSet attributeSet) {
+ super(context, attributeSet);
+ init();
+ }
+
+ public void init() {
+ SurfaceHolder holder = getHolder();
+ holder.addCallback(this);
+ holder.setFormat(PixelFormat.RGB_565);
+
+ mController = new GLController(this);
+ }
+
+ public void setRenderer(GLSurfaceView.Renderer renderer) {
+ mRenderer = renderer;
+ }
+
+ public GLSurfaceView.Renderer getRenderer() {
+ return mRenderer;
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public synchronized void requestRender() {
+ if (mGLThread != null) {
+ mGLThread.renderFrame();
+ }
+ if (mListener != null) {
+ mListener.renderRequested();
+ }
+ }
+
+ /**
+ * Creates a Java GL thread. After this is called, the FlexibleGLSurfaceView may be used just
+ * like a GLSurfaceView. It is illegal to access the controller after this has been called.
+ */
+ public synchronized void createGLThread() {
+ if (mGLThread != null) {
+ throw new FlexibleGLSurfaceViewException("createGLThread() called with a GL thread " +
+ "already in place!");
+ }
+
+ Log.e(LOGTAG, "### Creating GL thread!");
+ mGLThread = new GLThread(mController);
+ mGLThread.start();
+ notifyAll();
+ }
+
+ /**
+ * Destroys the Java GL thread. Returns a Thread that completes when the Java GL thread is
+ * fully shut down.
+ */
+ public synchronized Thread destroyGLThread() {
+ // Wait for the GL thread to be started.
+ Log.e(LOGTAG, "### Waiting for GL thread to be created...");
+ while (mGLThread == null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ Log.e(LOGTAG, "### Destroying GL thread!");
+ Thread glThread = mGLThread;
+ mGLThread.shutdown();
+ mGLThread = null;
+ return glThread;
+ }
+
+ public synchronized void recreateSurface() {
+ if (mGLThread == null) {
+ throw new FlexibleGLSurfaceViewException("recreateSurface() called with no GL " +
+ "thread active!");
+ }
+
+ mGLThread.recreateSurface();
+ }
+
+ public synchronized GLController getGLController() {
+ if (mGLThread != null) {
+ throw new FlexibleGLSurfaceViewException("getGLController() called with a GL thread " +
+ "active; shut down the GL thread first!");
+ }
+
+ return mController;
+ }
+
+ public synchronized void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height) {
+ mController.sizeChanged(width, height);
+ if (mGLThread != null) {
+ mGLThread.surfaceChanged(width, height);
+ }
+
+ if (mListener != null) {
+ mListener.surfaceChanged(width, height);
+ }
+ }
+
+ public synchronized void surfaceCreated(SurfaceHolder holder) {
+ mController.surfaceCreated();
+ if (mGLThread != null) {
+ mGLThread.surfaceCreated();
+ }
+ }
+
+ public synchronized void surfaceDestroyed(SurfaceHolder holder) {
+ mController.surfaceDestroyed();
+ if (mGLThread != null) {
+ mGLThread.surfaceDestroyed();
+ }
+
+ if (mListener != null) {
+ mListener.compositionPauseRequested();
+ }
+ }
+
+ // Called from the compositor thread
+ public static GLController registerCxxCompositor() {
+ try {
+ Log.e(LOGTAG, "### registerCxxCompositor point A");
+ System.out.println("register layer comp");
+ Log.e(LOGTAG, "### registerCxxCompositor point B");
+ FlexibleGLSurfaceView flexView = (FlexibleGLSurfaceView) /*GeckoApp*/LibreOfficeMainActivity.mAppContext.getLayerController().getView();
+ Log.e(LOGTAG, "### registerCxxCompositor point C: " + flexView);
+ try {
+ flexView.destroyGLThread().join();
+ } catch (InterruptedException e) {}
+ Log.e(LOGTAG, "### registerCxxCompositor point D: " + flexView.getGLController());
+ return flexView.getGLController();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "### Exception! " + e);
+ return null;
+ }
+ }
+
+ public interface Listener {
+ void renderRequested();
+ void compositionPauseRequested();
+ void compositionResumeRequested();
+ void surfaceChanged(int width, int height);
+ }
+
+ public static class FlexibleGLSurfaceViewException extends RuntimeException {
+ public static final long serialVersionUID = 1L;
+
+ FlexibleGLSurfaceViewException(String e) {
+ super(e);
+ }
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/FloatSize.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/FloatSize.java
new file mode 100644
index 000000000000..5fb73ec18df9
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/FloatSize.java
@@ -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>
+ * Chris Lord <chrislord.net@gmail.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.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.util.FloatUtils;
+
+public class FloatSize {
+ public final float width, height;
+
+ public FloatSize(FloatSize size) {
+ width = size.width;
+ height = size.height;
+ }
+
+ public FloatSize(IntSize size) {
+ width = size.width;
+ height = size.height;
+ }
+
+ public FloatSize(float aWidth, float aHeight) {
+ width = aWidth;
+ height = aHeight;
+ }
+
+ public FloatSize(JSONObject json) {
+ try {
+ width = (float) json.getDouble("width");
+ height = (float) json.getDouble("height");
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "(" + width + "," + height + ")";
+ }
+
+ public boolean isPositive() {
+ return (width > 0 && height > 0);
+ }
+
+ public boolean fuzzyEquals(FloatSize size) {
+ return (FloatUtils.fuzzyEquals(size.width, width) &&
+ FloatUtils.fuzzyEquals(size.height, height));
+ }
+
+ public FloatSize scale(float factor) {
+ return new FloatSize(width * factor, height * factor);
+ }
+
+ /*
+ * Returns the size that represents a linear transition between this size and `to` at time `t`,
+ * which is on the scale [0, 1).
+ */
+ public FloatSize interpolate(FloatSize to, float t) {
+ return new FloatSize(FloatUtils.interpolate(width, to.width, t),
+ FloatUtils.interpolate(height, to.height, t));
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLController.java
new file mode 100644
index 000000000000..e8f201228666
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLController.java
@@ -0,0 +1,279 @@
+/* -*- 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) 2011-2012
+ * 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 android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGL11;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+
+public class GLController {
+ private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+ private static final String LOGTAG = "GeckoGLController";
+
+ private FlexibleGLSurfaceView mView;
+ private int mGLVersion;
+ private boolean mSurfaceValid;
+ private int mWidth, mHeight;
+
+ private EGL10 mEGL;
+ private EGLDisplay mEGLDisplay;
+ private EGLConfig mEGLConfig;
+ private EGLContext mEGLContext;
+ private EGLSurface mEGLSurface;
+
+ private GL mGL;
+
+ private static final int LOCAL_EGL_OPENGL_ES2_BIT = 4;
+
+ private static final int[] CONFIG_SPEC = {
+ EGL10.EGL_RED_SIZE, 5,
+ EGL10.EGL_GREEN_SIZE, 6,
+ EGL10.EGL_BLUE_SIZE, 5,
+ EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
+ EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT,
+ EGL10.EGL_NONE
+ };
+
+ public GLController(FlexibleGLSurfaceView view) {
+ mView = view;
+ mGLVersion = 2;
+ mSurfaceValid = false;
+ }
+
+ public void setGLVersion(int version) {
+ mGLVersion = version;
+ }
+
+ /** You must call this on the same thread you intend to use OpenGL on. */
+ public void initGLContext() {
+ initEGLContext();
+ createEGLSurface();
+ }
+
+ public void disposeGLContext() {
+ if (!mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_CONTEXT)) {
+ throw new GLControllerException("EGL context could not be released!");
+ }
+
+ if (mEGLSurface != null) {
+ if (!mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface)) {
+ throw new GLControllerException("EGL surface could not be destroyed!");
+ }
+
+ mEGLSurface = null;
+ }
+
+ if (mEGLContext == null) {
+ if (!mEGL.eglDestroyContext(mEGLDisplay, mEGLContext)) {
+ throw new GLControllerException("EGL context could not be destroyed!");
+ }
+
+ mGL = null;
+ mEGLDisplay = null;
+ mEGLConfig = null;
+ mEGLContext = null;
+ }
+ }
+
+ public GL getGL() { return mEGLContext.getGL(); }
+ public EGLDisplay getEGLDisplay() { return mEGLDisplay; }
+ public EGLConfig getEGLConfig() { return mEGLConfig; }
+ public EGLContext getEGLContext() { return mEGLContext; }
+ public EGLSurface getEGLSurface() { return mEGLSurface; }
+ public FlexibleGLSurfaceView getView() { return mView; }
+
+ public boolean hasSurface() {
+ return mEGLSurface != null;
+ }
+
+ public boolean swapBuffers() {
+ return mEGL.eglSwapBuffers(mEGLDisplay, mEGLSurface);
+ }
+
+ public boolean checkForLostContext() {
+ if (mEGL.eglGetError() != EGL11.EGL_CONTEXT_LOST) {
+ return false;
+ }
+
+ mEGLDisplay = null;
+ mEGLConfig = null;
+ mEGLContext = null;
+ mEGLSurface = null;
+ mGL = null;
+ return true;
+ }
+
+ public synchronized void waitForValidSurface() {
+ while (!mSurfaceValid) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public synchronized int getWidth() {
+ return mWidth;
+ }
+
+ public synchronized int getHeight() {
+ return mHeight;
+ }
+
+ synchronized void surfaceCreated() {
+ mSurfaceValid = true;
+ notifyAll();
+ }
+
+ synchronized void surfaceDestroyed() {
+ mSurfaceValid = false;
+ notifyAll();
+ }
+
+ synchronized void sizeChanged(int newWidth, int newHeight) {
+ mWidth = newWidth;
+ mHeight = newHeight;
+ }
+
+ private void initEGL() {
+ mEGL = (EGL10)EGLContext.getEGL();
+
+ mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+ if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
+ throw new GLControllerException("eglGetDisplay() failed");
+ }
+
+ int[] version = new int[2];
+ if (!mEGL.eglInitialize(mEGLDisplay, version)) {
+ throw new GLControllerException("eglInitialize() failed");
+ }
+
+ mEGLConfig = chooseConfig();
+ }
+
+ private void initEGLContext() {
+ initEGL();
+
+ int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, mGLVersion, EGL10.EGL_NONE };
+ mEGLContext = mEGL.eglCreateContext(mEGLDisplay, mEGLConfig, EGL10.EGL_NO_CONTEXT,
+ attribList);
+ if (mEGLContext == null || mEGLContext == EGL10.EGL_NO_CONTEXT) {
+ throw new GLControllerException("createContext() failed");
+ }
+ }
+
+ private EGLConfig chooseConfig() {
+ int[] numConfigs = new int[1];
+ if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, null, 0, numConfigs) ||
+ numConfigs[0] <= 0) {
+ throw new GLControllerException("No available EGL configurations");
+ }
+
+ EGLConfig[] configs = new EGLConfig[numConfigs[0]];
+ if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, configs, numConfigs[0], numConfigs)) {
+ throw new GLControllerException("No EGL configuration for that specification");
+ }
+
+ // Select the first 565 RGB configuration.
+ int[] red = new int[1], green = new int[1], blue = new int[1];
+ for (EGLConfig config : configs) {
+ mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_RED_SIZE, red);
+ mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_GREEN_SIZE, green);
+ mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_BLUE_SIZE, blue);
+ if (red[0] == 5 && green[0] == 6 && blue[0] == 5) {
+ return config;
+ }
+ }
+
+ throw new GLControllerException("No suitable EGL configuration found");
+ }
+
+ private void createEGLSurface() {
+ SurfaceHolder surfaceHolder = mView.getHolder();
+ mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surfaceHolder, null);
+ if (mEGLSurface == null || mEGLSurface == EGL10.EGL_NO_SURFACE) {
+ throw new GLControllerException("EGL window surface could not be created!");
+ }
+
+ if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
+ throw new GLControllerException("EGL surface could not be made into the current " +
+ "surface!");
+ }
+
+ mGL = mEGLContext.getGL();
+
+ if (mView.getRenderer() != null) {
+ mView.getRenderer().onSurfaceCreated((GL10)mGL, mEGLConfig);
+ mView.getRenderer().onSurfaceChanged((GL10)mGL, mView.getWidth(), mView.getHeight());
+ }
+ }
+
+ // Provides an EGLSurface without assuming ownership of this surface.
+ private EGLSurface provideEGLSurface() {
+ if (mEGL == null) {
+ initEGL();
+ }
+
+ SurfaceHolder surfaceHolder = mView.getHolder();
+ mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surfaceHolder, null);
+ if (mEGLSurface == null || mEGLSurface == EGL10.EGL_NO_SURFACE) {
+ throw new GLControllerException("EGL window surface could not be created!");
+ }
+
+ return mEGLSurface;
+ }
+
+ public static class GLControllerException extends RuntimeException {
+ public static final long serialVersionUID = 1L;
+
+ GLControllerException(String e) {
+ super(e);
+ }
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLThread.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLThread.java
new file mode 100644
index 000000000000..4f788f64ebcb
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLThread.java
@@ -0,0 +1,181 @@
+/* -*- 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) 2011-2012
+ * 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 android.opengl.GLSurfaceView;
+import android.view.SurfaceHolder;
+import javax.microedition.khronos.opengles.GL10;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+
+// A GL thread managed by Java. It is not necessary to use this class to use the
+// FlexibleGLSurfaceView, but it can be helpful, especially if the GL rendering is to be done
+// entirely in Java.
+class GLThread extends Thread {
+ private LinkedBlockingQueue<Runnable> mQueue;
+ private GLController mController;
+ private boolean mRenderQueued;
+
+ public GLThread(GLController controller) {
+ mQueue = new LinkedBlockingQueue<Runnable>();
+ mController = controller;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ Runnable runnable;
+ try {
+ runnable = mQueue.take();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ runnable.run();
+ if (runnable instanceof ShutdownMessage) {
+ break;
+ }
+ }
+ }
+
+ public void recreateSurface() {
+ mQueue.add(new RecreateSurfaceMessage());
+ }
+
+ public void renderFrame() {
+ // Make sure there's only one render event in the queue at a time.
+ synchronized (this) {
+ if (!mRenderQueued) {
+ mQueue.add(new RenderFrameMessage());
+ mRenderQueued = true;
+ }
+ }
+ }
+
+ public void shutdown() {
+ mQueue.add(new ShutdownMessage());
+ }
+
+ public void surfaceChanged(int width, int height) {
+ mQueue.add(new SizeChangedMessage(width, height));
+ }
+
+ public void surfaceCreated() {
+ mQueue.add(new SurfaceCreatedMessage());
+ }
+
+ public void surfaceDestroyed() {
+ mQueue.add(new SurfaceDestroyedMessage());
+ }
+
+ private void doRecreateSurface() {
+ mController.disposeGLContext();
+ mController.initGLContext();
+ }
+
+ private GLSurfaceView.Renderer getRenderer() {
+ return mController.getView().getRenderer();
+ }
+
+ private class RecreateSurfaceMessage implements Runnable {
+ public void run() {
+ doRecreateSurface();
+ }
+ }
+
+ private class RenderFrameMessage implements Runnable {
+ public void run() {
+ synchronized (GLThread.this) {
+ mRenderQueued = false;
+ }
+
+ // Bail out if the surface was lost.
+ if (mController.getEGLSurface() == null) {
+ return;
+ }
+
+ GLSurfaceView.Renderer renderer = getRenderer();
+ if (renderer != null) {
+ renderer.onDrawFrame((GL10)mController.getGL());
+ }
+
+ mController.swapBuffers();
+ //if (!mController.swapBuffers() && mController.checkForLostContext()) {
+ // doRecreateSurface();
+ //}
+ }
+ }
+
+ private class ShutdownMessage implements Runnable {
+ public void run() {
+ mController.disposeGLContext();
+ mController = null;
+ }
+ }
+
+ private class SizeChangedMessage implements Runnable {
+ private int mWidth, mHeight;
+
+ public SizeChangedMessage(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ public void run() {
+ GLSurfaceView.Renderer renderer = getRenderer();
+ if (renderer != null) {
+ renderer.onSurfaceChanged((GL10)mController.getGL(), mWidth, mHeight);
+ }
+ }
+ }
+
+ private class SurfaceCreatedMessage implements Runnable {
+ public void run() {
+ if (!mController.hasSurface()) {
+ mController.initGLContext();
+ }
+ }
+ }
+
+ private class SurfaceDestroyedMessage implements Runnable {
+ public void run() {
+ mController.disposeGLContext();
+ }
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoGLLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoGLLayerClient.java
new file mode 100644
index 000000000000..9e4f376e7ad2
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoGLLayerClient.java
@@ -0,0 +1,265 @@
+/* -*- 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 android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+
+import org.libreoffice.LOEvent;
+import org.libreoffice.LOKitShell;
+
+public class GeckoGLLayerClient extends GeckoLayerClient
+ implements FlexibleGLSurfaceView.Listener, VirtualLayer.Listener {
+ private static final String LOGTAG = "GeckoGLLayerClient";
+
+ private LayerRenderer mLayerRenderer;
+ private boolean mLayerRendererInitialized;
+
+ public GeckoGLLayerClient(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Rect beginDrawing(int width, int height, int tileWidth, int tileHeight,
+ String metadata, boolean hasDirectTexture) {
+ Rect bufferRect = super.beginDrawing(width, height, tileWidth, tileHeight,
+ metadata, hasDirectTexture);
+ if (bufferRect == null) {
+ return null;
+ }
+
+ // Be sure to adjust the buffer size; if it's not at least as large as the viewport size,
+ // ViewportMetrics.getOptimumViewportOffset() gets awfully confused and severe display
+ // corruption results!
+ if (mBufferSize.width != width || mBufferSize.height != height) {
+ mBufferSize = new IntSize(width, height);
+ }
+
+ return bufferRect;
+ }
+
+ @Override
+ protected boolean handleDirectTextureChange(boolean hasDirectTexture) {
+ Log.e(LOGTAG, "### handleDirectTextureChange");
+ if (mTileLayer != null) {
+ return false;
+ }
+
+ Log.e(LOGTAG, "### Creating virtual layer");
+ VirtualLayer virtualLayer = new VirtualLayer();
+ virtualLayer.setListener(this);
+ virtualLayer.setSize(getBufferSize());
+ getLayerController().setRoot(virtualLayer);
+ mTileLayer = virtualLayer;
+
+ sendResizeEventIfNecessary(true);
+ return true;
+ }
+
+ @Override
+ public void setLayerController(LayerController layerController) {
+ super.setLayerController(layerController);
+
+ LayerView view = layerController.getView();
+ view.setListener(this);
+
+ mLayerRenderer = new LayerRenderer(view);
+ }
+
+ @Override
+ protected boolean shouldDrawProceed(int tileWidth, int tileHeight) {
+ Log.e(LOGTAG, "### shouldDrawProceed");
+ // Always draw.
+ return true;
+ }
+
+ @Override
+ protected void updateLayerAfterDraw(Rect updatedRect) {
+ Log.e(LOGTAG, "### updateLayerAfterDraw");
+ // Nothing to do.
+ }
+
+ @Override
+ protected IntSize getBufferSize() {
+ View view = (View) getLayerController().getView();
+ IntSize size = new IntSize(view.getWidth(), view.getHeight());
+ Log.e(LOGTAG, "### getBufferSize " + size);
+ return size;
+ }
+
+ @Override
+ protected IntSize getTileSize() {
+ Log.e(LOGTAG, "### getTileSize " + getBufferSize());
+ return getBufferSize();
+ }
+
+ @Override
+ protected void tileLayerUpdated() {
+ // Set the new origin and resolution instantly.
+ mTileLayer.performUpdates(null);
+ }
+
+ @Override
+ public Bitmap getBitmap() {
+ Log.e(LOGTAG, "### getBitmap");
+ IntSize size = getBufferSize();
+ try {
+ return Bitmap.createBitmap(size.width, size.height, Bitmap.Config.RGB_565);
+ } catch (OutOfMemoryError oom) {
+ Log.e(LOGTAG, "Unable to create bitmap", oom);
+ return null;
+ }
+ }
+
+ @Override
+ public int getType() {
+ Log.e(LOGTAG, "### getType");
+ return LAYER_CLIENT_TYPE_GL;
+ }
+
+ public void dimensionsChanged(Point newOrigin, float newResolution) {
+ Log.e(LOGTAG, "### dimensionsChanged " + newOrigin + " " + newResolution);
+ }
+
+ /* Informs Gecko that the screen size has changed. */
+ @Override
+ protected void sendResizeEventIfNecessary(boolean force) {
+ Log.e(LOGTAG, "### sendResizeEventIfNecessary " + force);
+
+ IntSize newSize = getBufferSize();
+ if (!force && mScreenSize != null && mScreenSize.equals(newSize)) {
+ return;
+ }
+
+ mScreenSize = newSize;
+
+ Log.e(LOGTAG, "### Screen-size changed to " + mScreenSize);
+ //GeckoEvent event = GeckoEvent.createSizeChangedEvent(mScreenSize.width, mScreenSize.height,
+ // mScreenSize.width, mScreenSize.height,
+ // mScreenSize.width, mScreenSize.height);
+ //GeckoAppShell.sendEventToGecko(event);
+ LOEvent event = LOEvent.sizeChanged(mScreenSize.width, mScreenSize.height,
+ mScreenSize.width, mScreenSize.height,
+ mScreenSize.width, mScreenSize.height);
+ LOKitShell.sendEvent(event);
+
+ }
+
+ /**
+ * For Gecko to use.
+ */
+ public ViewTransform getViewTransform() {
+ Log.e(LOGTAG, "### getViewTransform()");
+
+ // NB: We don't begin a transaction here because this can be called in a synchronous
+ // manner between beginDrawing() and endDrawing(), and that will cause a deadlock.
+
+ LayerController layerController = getLayerController();
+ synchronized (layerController) {
+ ViewportMetrics viewportMetrics = layerController.getViewportMetrics();
+ PointF viewportOrigin = viewportMetrics.getOrigin();
+ Point tileOrigin = mTileLayer.getOrigin();
+ float scrollX = viewportOrigin.x;
+ float scrollY = viewportOrigin.y;
+ float zoomFactor = viewportMetrics.getZoomFactor();
+ Log.e(LOGTAG, "### Viewport metrics = " + viewportMetrics + " tile reso = " +
+ mTileLayer.getResolution());
+ return new ViewTransform(scrollX, scrollY, zoomFactor);
+ }
+ }
+
+ public void renderRequested() {
+ Log.e(LOGTAG, "### Render requested, scheduling composite");
+ LOKitShell.scheduleComposite();
+ }
+
+ public void compositionPauseRequested() {
+ Log.e(LOGTAG, "### Scheduling PauseComposition");
+ LOKitShell.schedulePauseComposition();
+ }
+
+ public void compositionResumeRequested() {
+ Log.e(LOGTAG, "### Scheduling ResumeComposition");
+ LOKitShell.scheduleResumeComposition();
+ }
+
+ public void surfaceChanged(int width, int height) {
+ compositionPauseRequested();
+ LayerController layerController = getLayerController();
+ layerController.setViewportSize(new FloatSize(width, height));
+ compositionResumeRequested();
+ renderRequested();
+ }
+
+ /**
+ * For Gecko to use.
+ */
+ public LayerRenderer.Frame createFrame() {
+ // Create the shaders and textures if necessary.
+ if (!mLayerRendererInitialized) {
+ mLayerRenderer.createProgram();
+ mLayerRendererInitialized = true;
+ }
+
+ // Build the contexts and create the frame.
+ Layer.RenderContext pageContext = mLayerRenderer.createPageContext();
+ Layer.RenderContext screenContext = mLayerRenderer.createScreenContext();
+ return mLayerRenderer.createFrame(pageContext, screenContext);
+ }
+
+ /**
+ * For Gecko to use.
+ */
+ public void activateProgram() {
+ mLayerRenderer.activateProgram();
+ }
+
+ /**
+ * For Gecko to use.
+ */
+ public void deactivateProgram() {
+ mLayerRenderer.deactivateProgram();
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
new file mode 100644
index 000000000000..09349b4eea07
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -0,0 +1,414 @@
+/* -*- 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>
+ * Chris Lord <chrislord.net@gmail.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.libreoffice.LOEvent;
+import org.libreoffice.LOKitShell;
+import org.libreoffice.LibreOfficeMainActivity;
+import org.mozilla.gecko.util.FloatUtils;
+//import org.mozilla.gecko.GeckoApp;
+//import org.mozilla.gecko.GeckoAppShell;
+//import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.GeckoEventListener;
+import org.json.JSONException;
+import org.json.JSONObject;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class GeckoLayerClient extends LayerClient implements GeckoEventListener {
+ private static final String LOGTAG = "GeckoLayerClient";
+
+ public static final int LAYER_CLIENT_TYPE_NONE = 0;
+ public static final int LAYER_CLIENT_TYPE_SOFTWARE = 1;
+ public static final int LAYER_CLIENT_TYPE_GL = 2;
+
+ protected IntSize mScreenSize;
+ protected IntSize mBufferSize;
+
+ protected Layer mTileLayer;
+
+ /* The viewport that Gecko is currently displaying. */
+ protected ViewportMetrics mGeckoViewport;
+
+ /* The viewport that Gecko will display when drawing is finished */
+ protected ViewportMetrics mNewGeckoViewport;
+
+ private static final long MIN_VIEWPORT_CHANGE_DELAY = 25L;
+ private long mLastViewportChangeTime;
+ private boolean mPendingViewportAdjust;
+ private boolean mViewportSizeChanged;
+
+ // mUpdateViewportOnEndDraw is used to indicate that we received a
+ // viewport update notification while drawing. therefore, when the
+ // draw finishes, we need to update the entire viewport rather than
+ // just the page size. this boolean should always be accessed from
+ // inside a transaction, so no synchronization is needed.
+ private boolean mUpdateViewportOnEndDraw;
+
+ private String mLastCheckerboardColor;
+
+ private static Pattern sColorPattern;
+
+ /* Used by robocop for testing purposes */
+ private DrawListener mDrawListener;
+
+ protected abstract boolean handleDirectTextureChange(boolean hasDirectTexture);
+ protected abstract boolean shouldDrawProceed(int tileWidth, int tileHeight);
+ protected abstract void updateLayerAfterDraw(Rect updatedRect);
+ protected abstract IntSize getBufferSize();
+ protected abstract IntSize getTileSize();
+ protected abstract void tileLayerUpdated();
+ public abstract Bitmap getBitmap();
+ public abstract int getType();
+
+ public GeckoLayerClient(Context context) {
+ mScreenSize = new IntSize(0, 0);
+ mBufferSize = new IntSize(0, 0);
+ }
+
+ /** Attaches the root layer to the layer controller so that Gecko appears. */
+ @Override
+ public void setLayerController(LayerController layerController) {
+ super.setLayerController(layerController);
+
+ layerController.setRoot(mTileLayer);
+ if (mGeckoViewport != null) {
+ layerController.setViewportMetrics(mGeckoViewport);
+ }
+
+ //GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
+ //GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
+
+ sendResizeEventIfNecessary();
+ }
+
+ public Rect beginDrawing(int width, int height, int tileWidth, int tileHeight,
+ String metadata, boolean hasDirectTexture) {
+ Log.e(LOGTAG, "### beginDrawing " + width + " " + height + " " + tileWidth + " " +
+ tileHeight + " " + hasDirectTexture);
+
+ // If we've changed surface types, cancel this draw
+ if (handleDirectTextureChange(hasDirectTexture)) {
+ Log.e(LOGTAG, "### Cancelling draw due to direct texture change");
+ return null;
+ }
+
+ if (!shouldDrawProceed(tileWidth, tileHeight)) {
+ Log.e(LOGTAG, "### Cancelling draw due to shouldDrawProceed()");
+ return null;
+ }
+
+ LayerController controller = getLayerController();
+
+ try {
+ JSONObject viewportObject = new JSONObject(metadata);
+ mNewGeckoViewport = new ViewportMetrics(viewportObject);
+
+ Log.e(LOGTAG, "### beginDrawing new Gecko viewport " + mNewGeckoViewport);
+
+ // Update the background color, if it's present.
+ String backgroundColorString = viewportObject.optString("backgroundColor");
+ if (backgroundColorString != null && !backgroundColorString.equals(mLastCheckerboardColor)) {
+ mLastCheckerboardColor = backgroundColorString;
+ controller.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
+ }
+ } catch (JSONException e) {
+ Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
+ return null;
+ }
+
+
+ // Make sure we don't spend time painting areas we aren't interested in.
+ // Only do this if the Gecko viewport isn't going to override our viewport.
+ Rect bufferRect = new Rect(0, 0, width, height);
+
+ if (!mUpdateViewportOnEndDraw) {
+ // First, find out our ideal displayport. We do this by taking the
+ // clamped viewport origin and taking away the optimum viewport offset.
+ // This would be what we would send to Gecko if adjustViewport were
+ // called now.
+ ViewportMetrics currentMetrics = controller.getViewportMetrics();
+ PointF currentBestOrigin = RectUtils.getOrigin(currentMetrics.getClampedViewport());
+ PointF viewportOffset = currentMetrics.getOptimumViewportOffset(new IntSize(width, height));
+ currentBestOrigin.offset(-viewportOffset.x, -viewportOffset.y);
+
+ Rect currentRect = RectUtils.round(new RectF(currentBestOrigin.x, currentBestOrigin.y,
+ currentBestOrigin.x + width, currentBestOrigin.y + height));
+
+ // Second, store Gecko's displayport.
+ PointF currentOrigin = mNewGeckoViewport.getDisplayportOrigin();
+ bufferRect = RectUtils.round(new RectF(currentOrigin.x, currentOrigin.y,
+ currentOrigin.x + width, currentOrigin.y + height));
+
+
+ // Take the intersection of the two as the area we're interested in rendering.
+ if (!bufferRect.intersect(currentRect)) {
+ // If there's no intersection, we have no need to render anything,
+ // but make sure to update the viewport size.
+ beginTransaction(mTileLayer);
+ try {
+ updateViewport(true);
+ } finally {
+ endTransaction(mTileLayer);
+ }
+ return null;
+ }
+ bufferRect.offset(Math.round(-currentOrigin.x), Math.round(-currentOrigin.y));
+ }
+
+ beginTransaction(mTileLayer);
+ return bufferRect;
+ }
+
+ /*
+ * 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) {
+ synchronized (getLayerController()) {
+ try {
+ updateViewport(!mUpdateViewportOnEndDraw);
+ mUpdateViewportOnEndDraw = false;
+
+ Rect rect = new Rect(x, y, x + width, y + height);
+ updateLayerAfterDraw(rect);
+ } finally {
+ endTransaction(mTileLayer);
+ }
+ }
+ Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
+
+ /* Used by robocop for testing purposes */
+ if (mDrawListener != null) {
+ mDrawListener.drawFinished(x, y, width, height);
+ }
+ }
+
+ protected void updateViewport(boolean onlyUpdatePageSize) {
+ // save and restore the viewport size stored in java; never let the
+ // JS-side viewport dimensions override the java-side ones because
+ // java is the One True Source of this information, and allowing JS
+ // to override can lead to race conditions where this data gets clobbered.
+ FloatSize viewportSize = getLayerController().getViewportSize();
+ mGeckoViewport = mNewGeckoViewport;
+ mGeckoViewport.setSize(viewportSize);
+
+ LayerController controller = getLayerController();
+ PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
+ mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
+ mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
+
+ this.tileLayerUpdated();
+ Log.e(LOGTAG, "### updateViewport onlyUpdatePageSize=" + onlyUpdatePageSize +
+ " getTileViewport " + mGeckoViewport);
+
+ if (onlyUpdatePageSize) {
+ // Don't adjust page size when zooming unless zoom levels are
+ // approximately equal.
+ if (FloatUtils.fuzzyEquals(controller.getZoomFactor(),
+ mGeckoViewport.getZoomFactor()))
+ controller.setPageSize(mGeckoViewport.getPageSize());
+ } else {
+ controller.setViewportMetrics(mGeckoViewport);
+ controller.abortPanZoomAnimation();
+ }
+ }
+
+ /* Informs Gecko that the screen size has changed. */
+ protected void sendResizeEventIfNecessary(boolean force) {
+ Log.e(LOGTAG, "### sendResizeEventIfNecessary " + force);
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ /*GeckoApp*/LibreOfficeMainActivity.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+ // Return immediately if the screen size hasn't changed or the viewport
+ // size is zero (which indicates that the rendering surface hasn't been
+ // allocated yet).
+ boolean screenSizeChanged = (metrics.widthPixels != mScreenSize.width ||
+ metrics.heightPixels != mScreenSize.height);
+ boolean viewportSizeValid = (getLayerController() != null &&
+ getLayerController().getViewportSize().isPositive());
+ if (!(force || (screenSizeChanged && viewportSizeValid))) {
+ return;
+ }
+
+ mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
+ IntSize bufferSize = getBufferSize(), tileSize = getTileSize();
+
+ Log.e(LOGTAG, "### Screen-size changed to " + mScreenSize);
+ //GeckoEvent event = GeckoEvent.createSizeChangedEvent(bufferSize.width, bufferSize.height,
+ // metrics.widthPixels, metrics.heightPixels,
+ // tileSize.width, tileSize.height);
+ //GeckoAppShell.sendEventToGecko(event);
+ LOEvent event = LOEvent.sizeChanged(bufferSize.width, bufferSize.height,
+ metrics.widthPixels, metrics.heightPixels,
+ tileSize.width, tileSize.height);
+ }
+
+ // Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color
+ // cannot be parsed, returns white.
+ private static int parseColorFromGecko(String string) {
+ if (sColorPattern == null) {
+ sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
+ }
+
+ Matcher matcher = sColorPattern.matcher(string);
+ if (!matcher.matches()) {
+ return Color.WHITE;
+ }
+
+ int r = Integer.parseInt(matcher.group(1));
+ int g = Integer.parseInt(matcher.group(2));
+ int b = Integer.parseInt(matcher.group(3));
+ return Color.rgb(r, g, b);
+ }
+
+ @Override
+ public void render() {
+ adjustViewportWithThrottling();
+ }
+
+ private void adjustViewportWithThrottling() {
+ if (!getLayerController().getRedrawHint())
+ return;
+
+ if (mPendingViewportAdjust)
+ return;
+
+ long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime;
+ if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) {
+ getLayerController().getView().postDelayed(
+ new Runnable() {
+ public void run() {
+ mPendingViewportAdjust = false;
+ adjustViewport();
+ }
+ }, MIN_VIEWPORT_CHANGE_DELAY - timeDelta);
+ mPendingViewportAdjust = true;
+ return;
+ }
+
+ adjustViewport();
+ }
+
+ @Override
+ public void viewportSizeChanged() {
+ mViewportSizeChanged = true;
+ }
+
+ private void adjustViewport() {
+ ViewportMetrics viewportMetrics =
+ new ViewportMetrics(getLayerController().getViewportMetrics());
+
+ PointF viewportOffset = viewportMetrics.getOptimumViewportOffset(mBufferSize);
+ viewportMetrics.setViewportOffset(viewportOffset);
+ viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
+
+ //GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(viewportMetrics));
+ LOKitShell.sendEvent(LOEvent.viewport(viewportMetrics));
+ if (mViewportSizeChanged) {
+ mViewportSizeChanged = false;
+ //GeckoAppShell.viewSizeChanged();
+ LOKitShell.viewSizeChanged();
+ }
+
+ mLastViewportChangeTime = System.currentTimeMillis();
+ }
+
+ public void handleMessage(String event, JSONObject message) {
+ if ("Viewport:UpdateAndDraw".equals(event)) {
+ Log.e(LOGTAG, "### Java side Viewport:UpdateAndDraw()!");
+ mUpdateViewportOnEndDraw = true;
+
+ // Redraw everything.
+ Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
+ //GeckoAppShell.sendEventToGecko(GeckoEvent.createDrawEvent(rect));
+ LOKitShell.sendEvent(LOEvent.draw(rect));
+ } else if ("Viewport:UpdateLater".equals(event)) {
+ Log.e(LOGTAG, "### Java side Viewport:UpdateLater()!");
+ mUpdateViewportOnEndDraw = true;
+ }
+ }
+
+ @Override
+ public void geometryChanged() {
+ /* Let Gecko know if the screensize has changed */
+ sendResizeEventIfNecessary();
+ render();
+ }
+
+ public int getWidth() {
+ return mBufferSize.width;
+ }
+
+ public int getHeight() {
+ return mBufferSize.height;
+ }
+
+ public ViewportMetrics getGeckoViewportMetrics() {
+ // Return a copy, as we modify this inside the Gecko thread
+ if (mGeckoViewport != null)
+ return new ViewportMetrics(mGeckoViewport);
+ return null;
+ }
+
+ private void sendResizeEventIfNecessary() {
+ sendResizeEventIfNecessary(false);
+ }
+
+ /** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
+ public void setDrawListener(DrawListener listener) {
+ mDrawListener = listener;
+ }
+
+ /** Used by robocop for testing purposes. Not for production use! This is used via reflection by robocop. */
+ public interface DrawListener {
+ public void drawFinished(int x, int y, int width, int height);
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
new file mode 100644
index 000000000000..fa1d5adb3c70
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
@@ -0,0 +1,322 @@
+/* -*- 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>
+ * Chris Lord <chrislord.net@gmail.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.libreoffice.LOKitShell;
+import org.mozilla.gecko.gfx.CairoImage;
+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.MultiTileLayer;
+import org.mozilla.gecko.gfx.PointUtils;
+import org.mozilla.gecko.gfx.WidgetTileLayer;
+//import org.mozilla.gecko.GeckoAppShell;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.Log;
+import java.nio.ByteBuffer;
+
+/**
+ * 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 GeckoLayerClient {
+ private static final String LOGTAG = "GeckoSoftwareLayerClient";
+
+ private int mFormat;
+ private IntSize mViewportSize;
+ private ByteBuffer mBuffer;
+
+ private CairoImage mCairoImage;
+
+ private static final IntSize TILE_SIZE = new IntSize(256, 256);
+
+ // Whether or not the last paint we got used direct texturing
+ private boolean mHasDirectTexture;
+
+ public GeckoSoftwareLayerClient(Context context) {
+ super(context);
+
+ mFormat = CairoImage.FORMAT_ARGB32;
+
+ mCairoImage = new CairoImage() {
+ @Override
+ public ByteBuffer getBuffer() { return mBuffer; }
+ @Override
+ public IntSize getSize() { return mBufferSize; }
+ @Override
+ public int getFormat() { return mFormat; }
+ };
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (mBuffer != null)
+ LOKitShell.freeDirectBuffer(mBuffer);
+ mBuffer = null;
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public void setLayerController(LayerController layerController) {
+ super.setLayerController(layerController);
+
+ layerController.setRoot(mTileLayer);
+ if (mGeckoViewport != null) {
+ layerController.setViewportMetrics(mGeckoViewport);
+ }
+
+ //GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
+ //GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
+ //GeckoAppShell.registerGeckoEventListener("Checkerboard:Toggle", this);
+
+ // XXX: Review pcwalton. This signature changed on m-c, should force = false here?
+ sendResizeEventIfNecessary(false);
+ }
+
+ @Override
+ protected boolean handleDirectTextureChange(boolean hasDirectTexture) {
+ if (mTileLayer != null && hasDirectTexture == mHasDirectTexture)
+ return false;
+
+ mHasDirectTexture = hasDirectTexture;
+
+ if (mHasDirectTexture) {
+ Log.i(LOGTAG, "Creating WidgetTileLayer");
+ mTileLayer = new WidgetTileLayer(mCairoImage);
+ } else {
+ Log.i(LOGTAG, "Creating MultiTileLayer");
+ mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
+ }
+
+ getLayerController().setRoot(mTileLayer);
+
+ // Force a resize event to be sent because the results of this
+ // are different depending on what tile system we're using
+ sendResizeEventIfNecessary(true);
+
+ return true;
+ }
+
+ @Override
+ protected boolean shouldDrawProceed(int tileWidth, int tileHeight) {
+ // Make sure the tile-size matches. If it doesn't, we could crash trying
+ // to access invalid memory.
+ if (mHasDirectTexture) {
+ if (tileWidth != 0 || tileHeight != 0) {
+ Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" +
+ tileHeight);
+ return false;
+ }
+ } else {
+ if (tileWidth != TILE_SIZE.width || tileHeight != TILE_SIZE.height) {
+ Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" +
+ tileHeight);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public Rect beginDrawing(int width, int height, int tileWidth, int tileHeight,
+ String metadata, boolean hasDirectTexture) {
+ Rect bufferRect = super.beginDrawing(width, height, tileWidth, tileHeight,
+ metadata, hasDirectTexture);
+ if (bufferRect == null) {
+ return bufferRect;
+ }
+
+ // If the window size has changed, reallocate the buffer to match.
+ if (mBufferSize.width != width || mBufferSize.height != height) {
+ mBufferSize = new IntSize(width, height);
+
+ // Reallocate the buffer if necessary
+ if (mTileLayer instanceof MultiTileLayer) {
+ int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
+ int size = mBufferSize.getArea() * bpp;
+ if (mBuffer == null || mBuffer.capacity() != size) {
+ // Free the old buffer
+ if (mBuffer != null) {
+ LOKitShell.freeDirectBuffer(mBuffer);
+ mBuffer = null;
+ }
+
+ mBuffer = LOKitShell.allocateDirectBuffer(size);
+ }
+ }
+ }
+
+ return bufferRect;
+ }
+
+ @Override
+ protected void updateLayerAfterDraw(Rect updatedRect) {
+ if (!(mTileLayer instanceof MultiTileLayer)) {
+ return;
+ }
+
+ ((MultiTileLayer)mTileLayer).invalidate(updatedRect);
+ }
+
+ private void copyPixelsFromMultiTileLayer(Bitmap target) {
+ Canvas c = new Canvas(target);
+ ByteBuffer tileBuffer = mBuffer.slice();
+ int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
+
+ for (int y = 0; y < mBufferSize.height; y += TILE_SIZE.height) {
+ for (int x = 0; x < mBufferSize.width; x += TILE_SIZE.width) {
+ // Calculate tile size
+ IntSize tileSize = new IntSize(Math.min(mBufferSize.width - x, TILE_SIZE.width),
+ Math.min(mBufferSize.height - y, TILE_SIZE.height));
+
+ // Create a Bitmap from this tile
+ Bitmap tile = Bitmap.createBitmap(tileSize.width, tileSize.height,
+ CairoUtils.cairoFormatTobitmapConfig(mFormat));
+ tile.copyPixelsFromBuffer(tileBuffer.asIntBuffer());
+
+ // Copy the tile to the master Bitmap and recycle it
+ c.drawBitmap(tile, x, y, null);
+ tile.recycle();
+
+ // Progress the buffer to the next tile
+ tileBuffer.position(tileSize.getArea() * bpp);
+ tileBuffer = tileBuffer.slice();
+ }
+ }
+ }
+
+ @Override
+ protected void tileLayerUpdated() {
+ /* No-op. */
+ }
+
+ @Override
+ public Bitmap getBitmap() {
+ if (mTileLayer == null)
+ return null;
+
+ // Begin a tile transaction, otherwise the buffer can be destroyed while
+ // we're reading from it.
+ beginTransaction(mTileLayer);
+ try {
+ if (mBuffer == null || mBufferSize.width <= 0 || mBufferSize.height <= 0)
+ return null;
+ try {
+ Bitmap b = null;
+
+ if (mTileLayer instanceof MultiTileLayer) {
+ b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
+ CairoUtils.cairoFormatTobitmapConfig(mFormat));
+ copyPixelsFromMultiTileLayer(b);
+ } else {
+ Log.w(LOGTAG, "getBitmap() called on a layer (" + mTileLayer + ") we don't know how to get a bitmap from");
+ }
+
+ return b;
+ } catch (OutOfMemoryError oom) {
+ Log.w(LOGTAG, "Unable to create bitmap", oom);
+ return null;
+ }
+ } finally {
+ endTransaction(mTileLayer);
+ }
+ }
+
+ /** Returns the back buffer. This function is for Gecko to use. */
+ public ByteBuffer lockBuffer() {
+ return mBuffer;
+ }
+
+ /**
+ * Gecko calls this function to signal that it is done with the back buffer. After this call,
+ * it is forbidden for Gecko to touch the buffer.
+ */
+ public void unlockBuffer() {
+ /* no-op */
+ }
+
+ @Override
+ public int getType() {
+ return LAYER_CLIENT_TYPE_SOFTWARE;
+ }
+
+ @Override
+ protected IntSize getBufferSize() {
+ // Round up depending on layer implementation to remove texture wastage
+ if (!mHasDirectTexture) {
+ // Round to the next multiple of the tile size
+ return new IntSize(((mScreenSize.width + LayerController.MIN_BUFFER.width - 1) /
+ TILE_SIZE.width + 1) * TILE_SIZE.width,
+ ((mScreenSize.height + LayerController.MIN_BUFFER.height - 1) /
+ TILE_SIZE.height + 1) * TILE_SIZE.height);
+ }
+
+ int maxSize = getLayerController().getView().getMaxTextureSize();
+
+ // XXX Integrate gralloc/tiling work to circumvent this
+ if (mScreenSize.width > maxSize || mScreenSize.height > maxSize) {
+ throw new RuntimeException("Screen size of " + mScreenSize +
+ " larger than maximum texture size of " + maxSize);
+ }
+
+ // Round to next power of two until we have NPOT texture support, respecting maximum
+ // texture size
+ return new IntSize(Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.width +
+ LayerController.MIN_BUFFER.width)),
+ Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.height +
+ LayerController.MIN_BUFFER.height)));
+ }
+
+ @Override
+ protected IntSize getTileSize() {
+ // Round up depending on layer implementation to remove texture wastage
+ return !mHasDirectTexture ? TILE_SIZE : new IntSize(0, 0);
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/InputConnectionHandler.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/InputConnectionHandler.java
new file mode 100644
index 000000000000..6fef53fb0535
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/InputConnectionHandler.java
@@ -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);
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/IntSize.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/IntSize.java
new file mode 100644
index 000000000000..ee43b1bf557b
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/IntSize.java
@@ -0,0 +1,103 @@
+/* -*- 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.FloatSize;
+import org.json.JSONException;
+import org.json.JSONObject;
+import java.lang.Math;
+
+public class IntSize {
+ public final int width, height;
+
+ public IntSize(IntSize size) { width = size.width; height = size.height; }
+ public IntSize(int inWidth, int inHeight) { width = inWidth; height = inHeight; }
+
+ public IntSize(FloatSize size) {
+ width = Math.round(size.width);
+ height = Math.round(size.height);
+ }
+
+ public IntSize(JSONObject json) {
+ try {
+ width = json.getInt("width");
+ height = json.getInt("height");
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public int getArea() {
+ return width * height;
+ }
+
+ public boolean equals(IntSize size) {
+ return ((size.width == width) && (size.height == height));
+ }
+
+ public boolean isPositive() {
+ return (width > 0 && height > 0);
+ }
+
+ @Override
+ public String toString() { return "(" + width + "," + height + ")"; }
+
+ public IntSize scale(float factor) {
+ return new IntSize(Math.round(width * factor),
+ Math.round(height * factor));
+ }
+
+ /* Returns the power of two that is greater than or equal to value */
+ public static int nextPowerOfTwo(int value) {
+ // code taken from http://acius2.blogspot.com/2007/11/calculating-next-power-of-2.html
+ if (0 == value--) {
+ return 1;
+ }
+ value = (value >> 1) | value;
+ value = (value >> 2) | value;
+ value = (value >> 4) | value;
+ value = (value >> 8) | value;
+ value = (value >> 16) | value;
+ return value + 1;
+ }
+
+ public IntSize nextPowerOfTwo() {
+ return new IntSize(nextPowerOfTwo(width), nextPowerOfTwo(height));
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java
new file mode 100644
index 000000000000..2dd3316f0016
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java
@@ -0,0 +1,242 @@
+/* -*- 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>
+ * Chris Lord <chrislord.net@gmail.com>
+ * Arkady Blyakher <rkadyb@mit.edu>
+ *
+ * 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 android.graphics.Point;
+import android.graphics.RectF;
+import android.graphics.Region;
+
+import org.mozilla.gecko.util.FloatUtils;
+
+import java.nio.FloatBuffer;
+import java.util.concurrent.locks.ReentrantLock;
+
+public abstract class Layer {
+ private final ReentrantLock mTransactionLock;
+ protected Point mOrigin;
+ protected float mResolution;
+ private boolean mInTransaction;
+ private Point mNewOrigin;
+ private float mNewResolution;
+ private LayerView mView;
+
+ public Layer() {
+ mTransactionLock = new ReentrantLock();
+ mOrigin = new Point(0, 0);
+ mResolution = 1.0f;
+ }
+
+ /**
+ * Updates the layer. This returns false if there is still work to be done
+ * after this update.
+ */
+ public final boolean update(RenderContext context) {
+ if (mTransactionLock.isHeldByCurrentThread()) {
+ throw new RuntimeException("draw() called while transaction lock held by this " +
+ "thread?!");
+ }
+
+ if (mTransactionLock.tryLock()) {
+ try {
+ return performUpdates(context);
+ } finally {
+ mTransactionLock.unlock();
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Subclasses override this function to draw the layer.
+ */
+ public abstract void draw(RenderContext context);
+
+ /**
+ * Subclasses override this function to provide access to the size of the layer.
+ */
+ public abstract IntSize getSize();
+
+ /**
+ * Given the intrinsic size of the layer, returns the pixel boundaries of the layer rect.
+ */
+ protected RectF getBounds(RenderContext context, FloatSize size) {
+ float scaleFactor = context.zoomFactor / mResolution;
+ float x = mOrigin.x * scaleFactor, y = mOrigin.y * scaleFactor;
+ float width = size.width * scaleFactor, height = size.height * scaleFactor;
+ return new RectF(x, y, x + width, y + height);
+ }
+
+ /**
+ * Returns the region of the layer that is considered valid. The default
+ * implementation of this will return the bounds of the layer, but this
+ * may be overridden.
+ */
+ public Region getValidRegion(RenderContext context) {
+ return new Region(RectUtils.round(getBounds(context, new FloatSize(getSize()))));
+ }
+
+ /**
+ * Call this before modifying the layer. Note that, for TileLayers, "modifying the layer"
+ * includes altering the underlying CairoImage in any way. Thus you must call this function
+ * before modifying the byte buffer associated with this layer.
+ * <p/>
+ * This function may block, so you should never call this on the main UI thread.
+ */
+ public void beginTransaction(LayerView aView) {
+ if (mTransactionLock.isHeldByCurrentThread())
+ throw new RuntimeException("Nested transactions are not supported");
+ mTransactionLock.lock();
+ mView = aView;
+ mInTransaction = true;
+ mNewResolution = mResolution;
+ }
+
+ public void beginTransaction() {
+ beginTransaction(null);
+ }
+
+ /**
+ * Call this when you're done modifying the layer.
+ */
+ public void endTransaction() {
+ if (!mInTransaction)
+ throw new RuntimeException("endTransaction() called outside a transaction");
+ mInTransaction = false;
+ mTransactionLock.unlock();
+
+ if (mView != null)
+ mView.requestRender();
+ }
+
+ /**
+ * Returns true if the layer is currently in a transaction and false otherwise.
+ */
+ protected boolean inTransaction() {
+ return mInTransaction;
+ }
+
+ /**
+ * Returns the current layer origin.
+ */
+ public Point getOrigin() {
+ return mOrigin;
+ }
+
+ /**
+ * Sets the origin. Only valid inside a transaction.
+ */
+ public void setOrigin(Point newOrigin) {
+ if (!mInTransaction)
+ throw new RuntimeException("setOrigin() is only valid inside a transaction");
+ mNewOrigin = newOrigin;
+ }
+
+ /**
+ * Returns the current layer's resolution.
+ */
+ public float getResolution() {
+ return mResolution;
+ }
+
+ /**
+ * Sets the layer resolution. This value is used to determine how many pixels per
+ * device pixel this layer was rendered at. This will be reflected by scaling by
+ * the reciprocal of the resolution in the layer's transform() function.
+ * Only valid inside a transaction.
+ */
+ public void setResolution(float newResolution) {
+ if (!mInTransaction)
+ throw new RuntimeException("setResolution() is only valid inside a transaction");
+ mNewResolution = newResolution;
+ }
+
+ /**
+ * Subclasses may override this method to perform custom layer updates. This will be called
+ * with the transaction lock held. Subclass implementations of this method must call the
+ * superclass implementation. Returns false if there is still work to be done after this
+ * update is complete.
+ */
+ protected boolean performUpdates(RenderContext context) {
+ if (mNewOrigin != null) {
+ mOrigin = mNewOrigin;
+ mNewOrigin = null;
+ }
+ if (mNewResolution != 0.0f) {
+ mResolution = mNewResolution;
+ mNewResolution = 0.0f;
+ }
+
+ return true;
+ }
+
+ protected boolean dimensionChangesPending() {
+ return (mNewOrigin != null) || (mNewResolution != 0.0f);
+ }
+
+ public static class RenderContext {
+ public final RectF viewport;
+ public final FloatSize pageSize;
+ public final float zoomFactor;
+ public final int positionHandle;
+ public final int textureHandle;
+ public final FloatBuffer coordBuffer;
+
+ public RenderContext(RectF aViewport, FloatSize aPageSize, float aZoomFactor,
+ int aPositionHandle, int aTextureHandle, FloatBuffer aCoordBuffer) {
+ viewport = aViewport;
+ pageSize = aPageSize;
+ zoomFactor = aZoomFactor;
+ positionHandle = aPositionHandle;
+ textureHandle = aTextureHandle;
+ coordBuffer = aCoordBuffer;
+ }
+
+ public boolean fuzzyEquals(RenderContext other) {
+ if (other == null) {
+ return false;
+ }
+ return RectUtils.fuzzyEquals(viewport, other.viewport)
+ && pageSize.fuzzyEquals(other.pageSize)
+ && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor);
+ }
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerClient.java
new file mode 100644
index 000000000000..4f461088203e
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerClient.java
@@ -0,0 +1,81 @@
+/* -*- 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;
+
+/**
+ * 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 void viewportSizeChanged();
+
+ protected abstract void render();
+
+ public LayerController getLayerController() {
+ return mLayerController;
+ }
+
+ public void setLayerController(LayerController layerController) {
+ mLayerController = layerController;
+ }
+
+ /**
+ * A utility function for calling Layer.beginTransaction with the
+ * appropriate LayerView.
+ */
+ public void beginTransaction(Layer aLayer) {
+ if (mLayerController != null) {
+ LayerView view = mLayerController.getView();
+ if (view != null) {
+ aLayer.beginTransaction(view);
+ return;
+ }
+ }
+
+ aLayer.beginTransaction();
+ }
+
+ // Included for symmetry.
+ public void endTransaction(Layer aLayer) {
+ aLayer.endTransaction();
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
new file mode 100644
index 000000000000..250dc84c69fe
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
@@ -0,0 +1,534 @@
+/* -*- 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>
+ * Chris Lord <chrislord.net@gmail.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 android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View.OnTouchListener;
+
+import org.mozilla.gecko.ui.PanZoomController;
+import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * 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.
+ * <p/>
+ * Many methods require that the monitor be held, with a synchronized (controller) { ... } block.
+ */
+public class LayerController {
+ /* The extra area on the sides of the page that we want to buffer to help with
+ * smooth, asynchronous scrolling. Depending on a device's support for NPOT
+ * textures, this may be rounded up to the nearest power of two.
+ */
+ public static final IntSize MIN_BUFFER = new IntSize(512, 1024);
+ private static final String LOGTAG = "GeckoLayerController";
+ /* 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. */
+ private static final int DANGER_ZONE_X = 75;
+ private static final int DANGER_ZONE_Y = 150;
+ /* The time limit for pages to respond with preventDefault on touchevents
+ * before we begin panning the page */
+ private static final int PREVENT_DEFAULT_TIMEOUT = 200;
+ private Layer mRootLayer; /* The root layer. */
+ private LayerView mView; /* The main rendering view. */
+ /*
+ * The panning and zooming controller, which interprets pan and zoom gestures for us and
+ * updates our visible rect appropriately.
+ */
+ private Context mContext; /* The current context. */
+ private ViewportMetrics mViewportMetrics; /* The current viewport metrics. */
+ private boolean mWaitForTouchListeners;
+ private PanZoomController mPanZoomController;
+ private OnTouchListener mOnTouchListener; /* The touch listener. */
+ private LayerClient mLayerClient; /* The layer client. */
+ /* The new color for the checkerboard. */
+ private int mCheckerboardColor;
+ private boolean mCheckerboardShouldShowChecks;
+ private boolean mForceRedraw;
+ private boolean allowDefaultActions = true;
+ private Timer allowDefaultTimer = null;
+ private boolean inTouchSession = false;
+ private PointF initialTouchLocation = null;
+
+ public LayerController(Context context) {
+ mContext = context;
+
+ mForceRedraw = true;
+ mViewportMetrics = new ViewportMetrics();
+ mPanZoomController = new PanZoomController(this);
+ mView = new LayerView(context, this);
+ }
+
+ public void setForceRedraw() {
+ mForceRedraw = true;
+ }
+
+ public LayerClient getLayerClient() {
+ return mLayerClient;
+ }
+
+ public void setLayerClient(LayerClient layerClient) {
+ mLayerClient = layerClient;
+ layerClient.setLayerController(this);
+ }
+
+ public Layer getRoot() {
+ return mRootLayer;
+ }
+
+ public void setRoot(Layer layer) {
+ mRootLayer = layer;
+ }
+
+ public LayerView getView() {
+ return mView;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public ViewportMetrics getViewportMetrics() {
+ return mViewportMetrics;
+ }
+
+ /**
+ * Sets the entire viewport metrics at once. This function does not notify the layer client or
+ * the pan/zoom controller, so you will need to call notifyLayerClientOfGeometryChange() or
+ * notifyPanZoomControllerOfGeometryChange() after calling this. You must hold the monitor
+ * while calling this.
+ */
+ public void setViewportMetrics(ViewportMetrics viewport) {
+ mViewportMetrics = new ViewportMetrics(viewport);
+ Log.d(LOGTAG, "setViewportMetrics: " + mViewportMetrics);
+ // this function may or may not be called on the UI thread,
+ // but repositionPluginViews must only be called on the UI thread.
+ //GeckoApp.mAppContext.runOnUiThread(new Runnable() {
+ // public void run() {
+ // GeckoApp.mAppContext.repositionPluginViews(false);
+ // }
+ //});
+ mView.requestRender();
+ }
+
+ public RectF getViewport() {
+ return mViewportMetrics.getViewport();
+ }
+
+ public FloatSize getViewportSize() {
+ return mViewportMetrics.getSize();
+ }
+
+ /**
+ * The view calls this function to indicate that the viewport changed size. It must hold the
+ * monitor while calling it.
+ * <p/>
+ * 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 setViewportSize(FloatSize size) {
+ // Resize the viewport, and modify its zoom factor so that the page retains proportionally
+ // zoomed relative to the screen.
+ float oldHeight = mViewportMetrics.getSize().height;
+ float oldWidth = mViewportMetrics.getSize().width;
+ float oldZoomFactor = mViewportMetrics.getZoomFactor();
+ mViewportMetrics.setSize(size);
+
+ // if the viewport got larger (presumably because the vkb went away), and the page
+ // is smaller than the new viewport size, increase the page size so that the panzoomcontroller
+ // doesn't zoom in to make it fit (bug 718270). this page size change is in anticipation of
+ // gecko increasing the page size to match the new viewport size, which will happen the next
+ // time we get a draw update.
+ if (size.width >= oldWidth && size.height >= oldHeight) {
+ FloatSize pageSize = mViewportMetrics.getPageSize();
+ if (pageSize.width < size.width || pageSize.height < size.height) {
+ mViewportMetrics.setPageSize(new FloatSize(Math.max(pageSize.width, size.width),
+ Math.max(pageSize.height, size.height)));
+ }
+ }
+
+ PointF newFocus = new PointF(size.width / 2.0f, size.height / 2.0f);
+ float newZoomFactor = size.width * oldZoomFactor / oldWidth;
+ mViewportMetrics.scaleTo(newZoomFactor, newFocus);
+
+ Log.d(LOGTAG, "setViewportSize: " + mViewportMetrics);
+ setForceRedraw();
+
+ if (mLayerClient != null)
+ mLayerClient.viewportSizeChanged();
+
+ notifyLayerClientOfGeometryChange();
+ mPanZoomController.abortAnimation();
+ mView.requestRender();
+ }
+
+ public FloatSize getPageSize() {
+ return mViewportMetrics.getPageSize();
+ }
+
+ /**
+ * Sets the current page size. You must hold the monitor while calling this.
+ */
+ public void setPageSize(FloatSize size) {
+ if (mViewportMetrics.getPageSize().fuzzyEquals(size))
+ return;
+
+ mViewportMetrics.setPageSize(size);
+ Log.d(LOGTAG, "setPageSize: " + mViewportMetrics);
+
+ // Page size is owned by the LayerClient, so no need to notify it of
+ // this change.
+
+ mView.post(new Runnable() {
+ public void run() {
+ mPanZoomController.pageSizeUpdated();
+ mView.requestRender();
+ }
+ });
+ }
+
+ public PointF getOrigin() {
+ return mViewportMetrics.getOrigin();
+ }
+
+ public float getZoomFactor() {
+ return mViewportMetrics.getZoomFactor();
+ }
+
+ public Bitmap getBackgroundPattern() {
+ return getDrawable("background");
+ }
+
+ public Bitmap getShadowPattern() {
+ return getDrawable("shadow");
+ }
+
+ public PanZoomController getPanZoomController() {
+ return mPanZoomController;
+ }
+
+ public GestureDetector.OnGestureListener getGestureListener() {
+ return mPanZoomController;
+ }
+
+ public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() {
+ return mPanZoomController;
+ }
+
+ public GestureDetector.OnDoubleTapListener getDoubleTapListener() {
+ return mPanZoomController;
+ }
+
+ public 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);
+ }
+
+ public Bitmap getDrawable16(String name) {
+ Resources resources = mContext.getResources();
+ int resourceID = resources.getIdentifier(name, "drawable", mContext.getPackageName());
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inScaled = false;
+ options.inPreferredConfig = Bitmap.Config.RGB_565;
+ return BitmapFactory.decodeResource(mContext.getResources(), resourceID, options);
+ }
+
+ /**
+ * Scrolls the viewport by the given offset. You must hold the monitor while calling this.
+ */
+ public void scrollBy(PointF point) {
+ PointF origin = mViewportMetrics.getOrigin();
+ origin.offset(point.x, point.y);
+ mViewportMetrics.setOrigin(origin);
+ Log.d(LOGTAG, "scrollBy: " + mViewportMetrics);
+
+ notifyLayerClientOfGeometryChange();
+ //GeckoApp.mAppContext.repositionPluginViews(false);
+ mView.requestRender();
+ }
+
+ /**
+ * Scales the viewport, keeping the given focus point in the same place before and after the
+ * scale operation. You must hold the monitor while calling this.
+ */
+ public void scaleWithFocus(float zoomFactor, PointF focus) {
+ mViewportMetrics.scaleTo(zoomFactor, focus);
+ Log.d(LOGTAG, "scaleWithFocus: " + mViewportMetrics + "; zf=" + zoomFactor);
+
+ // We assume the zoom level will only be modified by the
+ // PanZoomController, so no need to notify it of this change.
+ notifyLayerClientOfGeometryChange();
+ //GeckoApp.mAppContext.repositionPluginViews(false);
+ mView.requestRender();
+ }
+
+ 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();
+ }
+
+ /**
+ * Aborts any pan/zoom animation that is currently in progress.
+ */
+ public void abortPanZoomAnimation() {
+ if (mPanZoomController != null) {
+ mView.post(new Runnable() {
+ public void run() {
+ mPanZoomController.abortAnimation();
+ }
+ });
+ }
+ }
+
+ /**
+ * 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() {
+ // FIXME: Allow redraw while a finger is down, but only if we're about to checkerboard.
+ // This requires fixing aboutToCheckerboard() to know about the new buffer size.
+
+ if (mForceRedraw) {
+ mForceRedraw = false;
+ return true;
+ }
+
+ return mPanZoomController.getRedrawHint();
+ }
+
+ private RectF getTileRect() {
+ if (mRootLayer == null)
+ return new RectF();
+
+ float x = mRootLayer.getOrigin().x, y = mRootLayer.getOrigin().y;
+ IntSize layerSize = mRootLayer.getSize();
+ return new RectF(x, y, x + layerSize.width, y + layerSize.height);
+ }
+
+ // Returns true if a checkerboard is about to be visible.
+ private boolean aboutToCheckerboard() {
+ // Increase the size of the viewport (and clamp to page boundaries), and
+ // intersect it with the tile's displayport to determine whether we're
+ // close to checkerboarding.
+ FloatSize pageSize = getPageSize();
+ RectF adjustedViewport = RectUtils.expand(getViewport(), DANGER_ZONE_X, DANGER_ZONE_Y);
+ if (adjustedViewport.top < 0) adjustedViewport.top = 0;
+ if (adjustedViewport.left < 0) adjustedViewport.left = 0;
+ if (adjustedViewport.right > pageSize.width) adjustedViewport.right = pageSize.width;
+ if (adjustedViewport.bottom > pageSize.height) adjustedViewport.bottom = pageSize.height;
+
+ return !getTileRect().contains(adjustedViewport);
+ }
+
+ /**
+ * 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 PointF convertViewPointToLayerPoint(PointF viewPoint) {
+ if (mRootLayer == null)
+ return null;
+
+ // Undo the transforms.
+ PointF origin = mViewportMetrics.getOrigin();
+ PointF newPoint = new PointF(origin.x, origin.y);
+ newPoint.offset(viewPoint.x, viewPoint.y);
+
+ Point rootOrigin = mRootLayer.getOrigin();
+ newPoint.offset(-rootOrigin.x, -rootOrigin.y);
+
+ return newPoint;
+ }
+
+ /*
+ * 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) {
+ int action = event.getAction();
+ PointF point = new PointF(event.getX(), event.getY());
+
+ if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+ mView.clearEventQueue();
+ initialTouchLocation = point;
+ allowDefaultActions = !mWaitForTouchListeners;
+ post(new Runnable() {
+ public void run() {
+ preventPanning(mWaitForTouchListeners);
+ }
+ });
+ }
+
+ // After the initial touch, ignore touch moves until they exceed a minimum distance.
+ if (initialTouchLocation != null && (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_MOVE) {
+ if (PointUtils.subtract(point, initialTouchLocation).length() > PanZoomController.PAN_THRESHOLD) {
+ initialTouchLocation = null;
+ } else {
+ return !allowDefaultActions;
+ }
+ }
+
+ if (mOnTouchListener != null)
+ mOnTouchListener.onTouch(mView, event);
+
+ if (!mWaitForTouchListeners)
+ return !allowDefaultActions;
+
+ boolean createTimer = false;
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
+ if (!inTouchSession && allowDefaultTimer == null) {
+ inTouchSession = true;
+ createTimer = true;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ // if we still have initialTouchLocation, we haven't fired any
+ // touchmove events. We should start the timer to wait for preventDefault
+ // from touchstart. If we don't hear from it we fire mouse events
+ if (initialTouchLocation != null)
+ createTimer = true;
+ inTouchSession = false;
+ }
+ }
+
+ if (createTimer) {
+ if (allowDefaultTimer != null) {
+ allowDefaultTimer.cancel();
+ }
+ allowDefaultTimer = new Timer();
+ allowDefaultTimer.schedule(new TimerTask() {
+ public void run() {
+ post(new Runnable() {
+ public void run() {
+ preventPanning(false);
+ }
+ });
+ }
+ }, PREVENT_DEFAULT_TIMEOUT);
+ }
+
+ return !allowDefaultActions;
+ }
+
+ public void preventPanning(boolean aValue) {
+ if (allowDefaultTimer != null) {
+ allowDefaultTimer.cancel();
+ allowDefaultTimer.purge();
+ allowDefaultTimer = null;
+ }
+ if (aValue == allowDefaultActions) {
+ allowDefaultActions = !aValue;
+
+ if (aValue) {
+ mView.clearEventQueue();
+ mPanZoomController.cancelTouch();
+ } else {
+ mView.processEventQueue();
+ }
+ }
+ }
+
+ public void setWaitForTouchListeners(boolean aValue) {
+ mWaitForTouchListeners = aValue;
+ }
+
+ /**
+ * Retrieves whether we should show checkerboard checks or not.
+ */
+ public boolean checkerboardShouldShowChecks() {
+ return mCheckerboardShouldShowChecks;
+ }
+
+ /**
+ * Retrieves the color that the checkerboard should be.
+ */
+ public int getCheckerboardColor() {
+ return mCheckerboardColor;
+ }
+
+ /**
+ * Sets a new color for the checkerboard.
+ */
+ public void setCheckerboardColor(int newColor) {
+ mCheckerboardColor = newColor;
+ mView.requestRender();
+ }
+
+ /**
+ * Sets whether or not the checkerboard should show checkmarks.
+ */
+ public void setCheckerboardShowChecks(boolean showChecks) {
+ mCheckerboardShouldShowChecks = showChecks;
+ mView.requestRender();
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
new file mode 100644
index 000000000000..6690aad8ec16
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
@@ -0,0 +1,689 @@
+/* -*- 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>
+ * Chris Lord <chrislord.net@gmail.com>
+ * Arkady Blyakher <rkadyb@mit.edu>
+ *
+ * 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.libreoffice.LOKitShell;
+import org.mozilla.gecko.gfx.BufferedCairoImage;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.Layer.RenderContext;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.NinePatchTileLayer;
+import org.mozilla.gecko.gfx.SingleTileLayer;
+import org.mozilla.gecko.gfx.TextureReaper;
+import org.mozilla.gecko.gfx.TextureGenerator;
+import org.mozilla.gecko.gfx.TextLayer;
+import org.mozilla.gecko.gfx.TileLayer;
+//import org.mozilla.gecko.GeckoAppShell;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.RegionIterator;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.os.SystemClock;
+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;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+
+/**
+ * The layer renderer implements the rendering logic for a layer view.
+ */
+public class LayerRenderer implements GLSurfaceView.Renderer {
+ private static final String LOGTAG = "GeckoLayerRenderer";
+ private static final String PROFTAG = "GeckoLayerRendererProf";
+
+ /*
+ * The amount of time a frame is allowed to take to render before we declare it a dropped
+ * frame.
+ */
+ private static final int MAX_FRAME_TIME = 16; /* 1000 ms / 60 FPS */
+
+ private static final int FRAME_RATE_METER_WIDTH = 64;
+ private static final int FRAME_RATE_METER_HEIGHT = 32;
+
+ private final LayerView mView;
+ private final SingleTileLayer mBackgroundLayer;
+ private final CheckerboardImage mCheckerboardImage;
+ private final SingleTileLayer mCheckerboardLayer;
+ private final NinePatchTileLayer mShadowLayer;
+ private final TextLayer mFrameRateLayer;
+ private final ScrollbarLayer mHorizScrollLayer;
+ private final ScrollbarLayer mVertScrollLayer;
+ private final FadeRunnable mFadeRunnable;
+ private final FloatBuffer mCoordBuffer;
+ private RenderContext mLastPageContext;
+ private int mMaxTextureSize;
+
+ private ArrayList<Layer> mExtraLayers = new ArrayList<Layer>();
+
+ // Dropped frames display
+ private int[] mFrameTimings;
+ private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames;
+ private boolean mShowFrameRate;
+
+ // Render profiling output
+ private int mFramesRendered;
+ private float mCompleteFramesRendered;
+ private boolean mProfileRender;
+ private long mProfileOutputTime;
+
+ /* Used by robocop for testing purposes */
+ private IntBuffer mPixelBuffer;
+
+ // Used by GLES 2.0
+ private int mProgram;
+ private int mPositionHandle;
+ private int mTextureHandle;
+ private int mSampleHandle;
+ private int mTMatrixHandle;
+
+ // column-major matrix applied to each vertex to shift the viewport from
+ // one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by
+ // a factor of 2 to fill up the screen
+ private static final float[] TEXTURE_MATRIX = {
+ 2.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 2.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 2.0f, 0.0f,
+ -1.0f, -1.0f, 0.0f, 1.0f
+ };
+
+ private static final int COORD_BUFFER_SIZE = 20;
+
+ // The shaders run on the GPU directly, the vertex shader is only applying the
+ // matrix transform detailed above
+ private static final String VERTEX_SHADER =
+ "uniform mat4 uTMatrix;\n" +
+ "attribute vec4 vPosition;\n" +
+ "attribute vec2 aTexCoord;\n" +
+ "varying vec2 vTexCoord;\n" +
+ "void main() {\n" +
+ " gl_Position = uTMatrix * vPosition;\n" +
+ " vTexCoord = aTexCoord;\n" +
+ "}\n";
+
+ // Note we flip the y-coordinate in the fragment shader from a
+ // coordinate system with (0,0) in the top left to one with (0,0) in
+ // the bottom left.
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "varying vec2 vTexCoord;\n" +
+ "uniform sampler2D sTexture;\n" +
+ "void main() {\n" +
+ " gl_FragColor = texture2D(sTexture, vec2(vTexCoord.x, 1.0 - vTexCoord.y));\n" +
+ "}\n";
+
+ public LayerRenderer(LayerView view) {
+ mView = view;
+
+ LayerController controller = view.getController();
+
+ CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern());
+ mBackgroundLayer = new SingleTileLayer(true, backgroundImage);
+
+ mCheckerboardImage = new CheckerboardImage();
+ mCheckerboardLayer = new SingleTileLayer(true, mCheckerboardImage);
+
+ CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern());
+ mShadowLayer = new NinePatchTileLayer(shadowImage);
+
+ IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT);
+ mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--");
+
+ mHorizScrollLayer = ScrollbarLayer.create(false);
+ mVertScrollLayer = ScrollbarLayer.create(true);
+ mFadeRunnable = new FadeRunnable();
+
+ mFrameTimings = new int[60];
+ mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
+ mShowFrameRate = false;
+
+ // Initialize the FloatBuffer that will be used to store all vertices and texture
+ // coordinates in draw() commands.
+ ByteBuffer byteBuffer = LOKitShell.allocateDirectBuffer(COORD_BUFFER_SIZE * 4);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ mCoordBuffer = byteBuffer.asFloatBuffer();
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ checkMonitoringEnabled();
+ createProgram();
+ activateProgram();
+ }
+
+ public void createProgram() {
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
+ int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
+
+ mProgram = GLES20.glCreateProgram();
+ GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
+ GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
+ GLES20.glLinkProgram(mProgram); // creates OpenGL program executables
+
+ // Get handles to the vertex shader's vPosition, aTexCoord, sTexture, and uTMatrix members.
+ mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
+ mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
+ mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
+ mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix");
+
+ int maxTextureSizeResult[] = new int[1];
+ GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
+ mMaxTextureSize = maxTextureSizeResult[0];
+ }
+
+ // Activates the shader program.
+ public void activateProgram() {
+ // Add the program to the OpenGL environment
+ GLES20.glUseProgram(mProgram);
+
+ // Set the transformation matrix
+ GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, TEXTURE_MATRIX, 0);
+
+ Log.e(LOGTAG, "### Position handle is " + mPositionHandle + ", texture handle is " +
+ mTextureHandle + ", last error is " + GLES20.glGetError());
+
+ // Enable the arrays from which we get the vertex and texture coordinates
+ GLES20.glEnableVertexAttribArray(mPositionHandle);
+ GLES20.glEnableVertexAttribArray(mTextureHandle);
+
+ GLES20.glUniform1i(mSampleHandle, 0);
+
+ TextureGenerator.get().fill();
+
+ // TODO: Move these calls into a separate deactivate() call that is called after the
+ // underlay and overlay are rendered.
+ }
+
+ // Deactivates the shader program. This must be done to avoid crashes after returning to the
+ // Gecko C++ compositor from Java.
+ public void deactivateProgram() {
+ GLES20.glDisableVertexAttribArray(mTextureHandle);
+ GLES20.glDisableVertexAttribArray(mPositionHandle);
+ GLES20.glUseProgram(0);
+ }
+
+ public int getMaxTextureSize() {
+ return mMaxTextureSize;
+ }
+
+ public void addLayer(Layer layer) {
+ LayerController controller = mView.getController();
+
+ synchronized (controller) {
+ if (mExtraLayers.contains(layer)) {
+ mExtraLayers.remove(layer);
+ }
+
+ mExtraLayers.add(layer);
+ }
+ }
+
+ public void removeLayer(Layer layer) {
+ LayerController controller = mView.getController();
+
+ synchronized (controller) {
+ mExtraLayers.remove(layer);
+ }
+ }
+
+ /**
+ * Called whenever a new frame is about to be drawn.
+ */
+ public void onDrawFrame(GL10 gl) {
+ RenderContext pageContext = createPageContext(), screenContext = createScreenContext();
+ Frame frame = createFrame(pageContext, screenContext);
+ synchronized (mView.getController()) {
+ frame.beginDrawing();
+ frame.drawBackground();
+ frame.drawRootLayer();
+ frame.drawForeground();
+ frame.endDrawing();
+ }
+ }
+
+ private void printCheckerboardStats() {
+ Log.d(PROFTAG, "Frames rendered over last 1000ms: " + mCompleteFramesRendered + "/" + mFramesRendered);
+ mFramesRendered = 0;
+ mCompleteFramesRendered = 0;
+ }
+
+ /** Used by robocop for testing purposes. Not for production use! */
+ IntBuffer getPixels() {
+ IntBuffer pixelBuffer = IntBuffer.allocate(mView.getWidth() * mView.getHeight());
+ synchronized (pixelBuffer) {
+ mPixelBuffer = pixelBuffer;
+ mView.requestRender();
+ try {
+ pixelBuffer.wait();
+ } catch (InterruptedException ie) {
+ }
+ mPixelBuffer = null;
+ }
+ return pixelBuffer;
+ }
+
+ public RenderContext createScreenContext() {
+ LayerController layerController = mView.getController();
+ IntSize viewportSize = new IntSize(layerController.getViewportSize());
+ RectF viewport = new RectF(0.0f, 0.0f, viewportSize.width, viewportSize.height);
+ FloatSize pageSize = new FloatSize(layerController.getPageSize());
+ return createContext(viewport, pageSize, 1.0f);
+ }
+
+ public RenderContext createPageContext() {
+ LayerController layerController = mView.getController();
+
+ Rect viewport = new Rect();
+ layerController.getViewport().round(viewport);
+
+ FloatSize pageSize = new FloatSize(layerController.getPageSize());
+ float zoomFactor = layerController.getZoomFactor();
+ return createContext(new RectF(viewport), pageSize, zoomFactor);
+ }
+
+ private RenderContext createContext(RectF viewport, FloatSize pageSize, float zoomFactor) {
+ return new RenderContext(viewport, pageSize, zoomFactor, mPositionHandle, mTextureHandle,
+ mCoordBuffer);
+ }
+
+ private Rect getPageRect() {
+ LayerController controller = mView.getController();
+
+ Point origin = PointUtils.round(controller.getOrigin());
+ IntSize pageSize = new IntSize(controller.getPageSize());
+
+ origin.negate();
+
+ return new Rect(origin.x, origin.y,
+ origin.x + pageSize.width, origin.y + pageSize.height);
+ }
+
+ private Rect transformToScissorRect(Rect rect) {
+ LayerController controller = mView.getController();
+ IntSize screenSize = new IntSize(controller.getViewportSize());
+
+ int left = Math.max(0, rect.left);
+ int top = Math.max(0, rect.top);
+ int right = Math.min(screenSize.width, rect.right);
+ int bottom = Math.min(screenSize.height, rect.bottom);
+
+ return new Rect(left, screenSize.height - bottom, right,
+ (screenSize.height - bottom) + (bottom - top));
+ }
+
+ public void onSurfaceChanged(GL10 gl, final int width, final int height) {
+ GLES20.glViewport(0, 0, width, height);
+
+ // updating the state in the view/controller/client should be
+ // done on the main UI thread, not the GL renderer thread
+ mView.post(new Runnable() {
+ public void run() {
+ mView.setViewportSize(new IntSize(width, height));
+ moveFrameRateLayer(width, height);
+ }
+ });
+
+ /* TODO: Throw away tile images? */
+ }
+
+ private void updateDroppedFrames(long frameStartTime) {
+ int frameElapsedTime = (int)(SystemClock.uptimeMillis() - frameStartTime);
+
+ /* Update the running statistics. */
+ mFrameTimingsSum -= mFrameTimings[mCurrentFrame];
+ mFrameTimingsSum += frameElapsedTime;
+ mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME;
+ mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME;
+
+ mFrameTimings[mCurrentFrame] = frameElapsedTime;
+ mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length;
+
+ int averageTime = mFrameTimingsSum / mFrameTimings.length;
+ mFrameRateLayer.beginTransaction();
+ try {
+ mFrameRateLayer.setText(averageTime + " ms/" + mDroppedFrames);
+ } finally {
+ mFrameRateLayer.endTransaction();
+ }
+ }
+
+ /* Given the new dimensions for the surface, moves the frame rate layer appropriately. */
+ private void moveFrameRateLayer(int width, int height) {
+ mFrameRateLayer.beginTransaction();
+ try {
+ Point origin = new Point(width - FRAME_RATE_METER_WIDTH - 8,
+ height - FRAME_RATE_METER_HEIGHT + 8);
+ mFrameRateLayer.setOrigin(origin);
+ } finally {
+ mFrameRateLayer.endTransaction();
+ }
+ }
+
+ private void checkMonitoringEnabled() {
+ /* Do this I/O off the main thread to minimize its impact on startup time. */
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Context context = mView.getContext();
+ SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0);
+ mShowFrameRate = preferences.getBoolean("showFrameRate", false);
+ mProfileRender = Log.isLoggable(PROFTAG, Log.DEBUG);
+ }
+ }).start();
+ }
+
+ private void updateCheckerboardLayer(RenderContext renderContext) {
+ int checkerboardColor = mView.getController().getCheckerboardColor();
+ boolean showChecks = mView.getController().checkerboardShouldShowChecks();
+ if (checkerboardColor == mCheckerboardImage.getColor() &&
+ showChecks == mCheckerboardImage.getShowChecks()) {
+ return;
+ }
+
+ mCheckerboardLayer.beginTransaction();
+ try {
+ mCheckerboardImage.update(showChecks, checkerboardColor);
+ mCheckerboardLayer.invalidate();
+ } finally {
+ mCheckerboardLayer.endTransaction();
+ }
+
+ mCheckerboardLayer.update(renderContext);
+ }
+
+ /*
+ * create a vertex shader type (GLES20.GL_VERTEX_SHADER)
+ * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
+ */
+ private int loadShader(int type, String shaderCode) {
+ int shader = GLES20.glCreateShader(type);
+ GLES20.glShaderSource(shader, shaderCode);
+ GLES20.glCompileShader(shader);
+ return shader;
+ }
+
+ public Frame createFrame(RenderContext pageContext, RenderContext screenContext) {
+ return new Frame(pageContext, screenContext);
+ }
+
+ class FadeRunnable implements Runnable {
+ private boolean mStarted;
+ private long mRunAt;
+
+ void scheduleStartFade(long delay) {
+ mRunAt = SystemClock.elapsedRealtime() + delay;
+ if (!mStarted) {
+ mView.postDelayed(this, delay);
+ mStarted = true;
+ }
+ }
+
+ void scheduleNextFadeFrame() {
+ if (mStarted) {
+ Log.e(LOGTAG, "scheduleNextFadeFrame() called while scheduled for starting fade");
+ }
+ mView.postDelayed(this, 1000L / 60L); // request another frame at 60fps
+ }
+
+ boolean timeToFade() {
+ return !mStarted;
+ }
+
+ public void run() {
+ long timeDelta = mRunAt - SystemClock.elapsedRealtime();
+ if (timeDelta > 0) {
+ // the run-at time was pushed back, so reschedule
+ mView.postDelayed(this, timeDelta);
+ } else {
+ // reached the run-at time, execute
+ mStarted = false;
+ mView.requestRender();
+ }
+ }
+ }
+
+ public class Frame {
+ // The timestamp recording the start of this frame.
+ private long mFrameStartTime;
+ // A rendering context for page-positioned layers, and one for screen-positioned layers.
+ private RenderContext mPageContext, mScreenContext;
+ // Whether a layer was updated.
+ private boolean mUpdated;
+
+ public Frame(RenderContext pageContext, RenderContext screenContext) {
+ mPageContext = pageContext;
+ mScreenContext = screenContext;
+ }
+
+ private void setScissorRect() {
+ Rect scissorRect = transformToScissorRect(getPageRect());
+ GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+ GLES20.glScissor(scissorRect.left, scissorRect.top,
+ scissorRect.width(), scissorRect.height());
+ }
+
+ public void beginDrawing() {
+ mFrameStartTime = SystemClock.uptimeMillis();
+
+ TextureReaper.get().reap();
+ TextureGenerator.get().fill();
+
+ mUpdated = true;
+
+ LayerController controller = mView.getController();
+ Layer rootLayer = controller.getRoot();
+
+ if (!mPageContext.fuzzyEquals(mLastPageContext)) {
+ // the viewport or page changed, so show the scrollbars again
+ // as per UX decision
+ mVertScrollLayer.unfade();
+ mHorizScrollLayer.unfade();
+ mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
+ } else if (mFadeRunnable.timeToFade()) {
+ boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
+ if (stillFading) {
+ mFadeRunnable.scheduleNextFadeFrame();
+ }
+ }
+ mLastPageContext = mPageContext;
+
+ /* Update layers. */
+ if (rootLayer != null) mUpdated &= rootLayer.update(mPageContext);
+ mUpdated &= mBackgroundLayer.update(mScreenContext);
+ mUpdated &= mShadowLayer.update(mPageContext);
+ updateCheckerboardLayer(mScreenContext);
+ mUpdated &= mFrameRateLayer.update(mScreenContext);
+ mUpdated &= mVertScrollLayer.update(mPageContext);
+ mUpdated &= mHorizScrollLayer.update(mPageContext);
+
+ for (Layer layer : mExtraLayers)
+ mUpdated &= layer.update(mPageContext);
+
+ GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+
+ // If a layer update requires further work, schedule another redraw
+ if (!mUpdated)
+ mView.requestRender();
+
+ PanningPerfAPI.recordFrameTime();
+
+ /* Used by robocop for testing purposes */
+ IntBuffer pixelBuffer = mPixelBuffer;
+ if (mUpdated && pixelBuffer != null) {
+ synchronized (pixelBuffer) {
+ pixelBuffer.position(0);
+ GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(),
+ (int)mScreenContext.viewport.height(), GLES20.GL_RGBA,
+ GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
+ pixelBuffer.notify();
+ }
+ }
+ }
+
+ public void drawBackground() {
+ /* Draw the background. */
+ mBackgroundLayer.draw(mScreenContext);
+
+ /* Draw the drop shadow, if we need to. */
+ Rect pageRect = getPageRect();
+ RectF untransformedPageRect = new RectF(0.0f, 0.0f, pageRect.width(),
+ pageRect.height());
+ if (!untransformedPageRect.contains(mView.getController().getViewport()))
+ mShadowLayer.draw(mPageContext);
+
+ /* Draw the checkerboard. */
+ setScissorRect();
+ mCheckerboardLayer.draw(mScreenContext);
+ GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+ }
+
+ // Draws the layer the client added to us.
+ void drawRootLayer() {
+ Layer rootLayer = mView.getController().getRoot();
+ if (rootLayer == null) {
+ return;
+ }
+
+ setScissorRect();
+ rootLayer.draw(mPageContext);
+ GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+ }
+
+ public void drawForeground() {
+ Rect pageRect = getPageRect();
+ LayerController controller = mView.getController();
+
+ /* Draw any extra layers that were added (likely plugins) */
+ for (Layer layer : mExtraLayers)
+ layer.draw(mPageContext);
+
+ /* Draw the vertical scrollbar. */
+ IntSize screenSize = new IntSize(controller.getViewportSize());
+ if (pageRect.height() > screenSize.height)
+ mVertScrollLayer.draw(mPageContext);
+
+ /* Draw the horizontal scrollbar. */
+ if (pageRect.width() > screenSize.width)
+ mHorizScrollLayer.draw(mPageContext);
+
+ /* Measure how much of the screen is checkerboarding */
+ Layer rootLayer = controller.getRoot();
+ if ((rootLayer != null) &&
+ (mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) {
+ // Find out how much of the viewport area is valid
+ Rect viewport = RectUtils.round(mPageContext.viewport);
+ Region validRegion = rootLayer.getValidRegion(mPageContext);
+ validRegion.op(viewport, Region.Op.INTERSECT);
+
+ float checkerboard = 0.0f;
+ if (!(validRegion.isRect() && validRegion.getBounds().equals(viewport))) {
+ int screenArea = viewport.width() * viewport.height();
+ validRegion.op(viewport, Region.Op.REVERSE_DIFFERENCE);
+
+ // XXX The assumption here is that a Region never has overlapping
+ // rects. This is true, as evidenced by reading the SkRegion
+ // source, but is not mentioned in the Android documentation,
+ // and so is liable to change.
+ // If it does change, this code will need to be reevaluated.
+ Rect r = new Rect();
+ int checkerboardArea = 0;
+ for (RegionIterator i = new RegionIterator(validRegion); i.next(r);) {
+ checkerboardArea += r.width() * r.height();
+ }
+
+ checkerboard = checkerboardArea / (float)screenArea;
+ }
+
+ PanningPerfAPI.recordCheckerboard(checkerboard);
+
+ mCompleteFramesRendered += 1.0f - checkerboard;
+ mFramesRendered ++;
+
+ if (mFrameStartTime - mProfileOutputTime > 1000) {
+ mProfileOutputTime = mFrameStartTime;
+ printCheckerboardStats();
+ }
+ }
+
+ /* Draw the FPS. */
+ if (mShowFrameRate) {
+ updateDroppedFrames(mFrameStartTime);
+
+ try {
+ GLES20.glEnable(GLES20.GL_BLEND);
+ GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+ mFrameRateLayer.draw(mScreenContext);
+ } finally {
+ GLES20.glDisable(GLES20.GL_BLEND);
+ }
+ }
+ }
+
+ public void endDrawing() {
+ // If a layer update requires further work, schedule another redraw
+ if (!mUpdated)
+ mView.requestRender();
+
+ PanningPerfAPI.recordFrameTime();
+
+ /* Used by robocop for testing purposes */
+ IntBuffer pixelBuffer = mPixelBuffer;
+ if (mUpdated && pixelBuffer != null) {
+ synchronized (pixelBuffer) {
+ pixelBuffer.position(0);
+ GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(),
+ (int)mScreenContext.viewport.height(), GLES20.GL_RGBA,
+ GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
+ pixelBuffer.notify();
+ }
+ }
+ }
+ }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
new file mode 100644
index 000000000000..a3c04fa600f2
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
@@ -0,0 +1,230 @@
+/* -*- 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>
+ * Arkady Blyakher <rkadyb@mit.edu>
+ *
+ * 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.GeckoInputConnection;
+import org.mozilla.gecko.gfx.FloatSize;
+import org.mozilla.gecko.gfx.InputConnectionHandler;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.view.View;
+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;
+import android.widget.RelativeLayout;
+import android.util.Log;
+import java.nio.IntBuffer;
+import java.util.LinkedList;
+
+/**
+ * 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 FlexibleGLSurfaceView {
+ private Context mContext;
+ private LayerController mController;
+ private InputConnectionHandler mInputConnectionHandler;
+ private LayerRenderer mRenderer;
+ private GestureDetector mGestureDetector;
+ private SimpleScaleGestureDetector mScaleGestureDetector;
+ private long mRenderTime;
+ private boolean mRenderTimeReset;
+ private static String LOGTAG = "GeckoLayerView";
+ /* List of events to be processed if the page does not prevent them. Should only be touched on the main thread */
+ private LinkedList<MotionEvent> mEventQueue = new LinkedList<MotionEvent>();
+
+
+ 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 SimpleScaleGestureDetector(controller.getScaleGestureListener());
+ mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener());
+ mInputConnectionHandler = null;
+
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+
+ createGLThread();
+ }
+
+ private void addToEventQueue(MotionEvent event) {
+ MotionEvent copy = MotionEvent.obtain(event);
+ mEventQueue.add(copy);
+ }
+
+ public void processEventQueue() {
+ MotionEvent event = mEventQueue.poll();
+ while(event != null) {
+ processEvent(event);
+ event = mEventQueue.poll();
+ }
+ }
+
+ public void clearEventQueue() {
+ mEventQueue.clear();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mController.onTouchEvent(event)) {
+ addToEventQueue(event);
+ return true;
+ }
+ return processEvent(event);
+ }
+
+ private boolean processEvent(MotionEvent event) {
+ if (mGestureDetector.onTouchEvent(event))
+ return true;
+ mScaleGestureDetector.onTouchEvent(event);
+ if (mScaleGestureDetector.isInProgress())
+ return true;
+ mController.getPanZoomController().onTouchEvent(event);
+ return true;
+ }
+
+ public LayerController getController() { return mController; }
+
+ /** The LayerRenderer calls this to indicate that the window has changed size. */
+ public void setViewportSize(IntSize size) {
+ mController.setViewportSize(new FloatSize(size));
+ }
+
+ //public GeckoInputConnection setInputConnectionHandler() {
+ // GeckoInputConnection geckoInputConnection = GeckoInputConnection.create(this);
+ // mInputConnectionHandler = geckoInputConnection;
+ // return geckoInputConnection;
+ //}
+
+ @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;
+ }
+
+ @Override
+ public void requestRender() {
+ super.requestRender();
+
+ synchronized(this) {
+ if (!mRenderTimeReset) {
+ mRenderTimeReset = true;
+ mRenderTime = System.nanoTime();
+ }
+ }
+ }
+
+ public void addLayer(Layer layer) {
+ mRenderer.addLayer(layer);
+ }
+
+ public void removeLayer(Layer layer) {
+ mRenderer.removeLayer(layer);
+ }
+
+ /**
+ * Returns the time elapsed between the first call of requestRender() after
+ * the last call of getRenderTime(), in nanoseconds.
+ */
+ public long getRenderTime() {
+ synchronized(this) {
+ mRenderTimeReset = false;
+ return System.nanoTime() - mRenderTime;
+ }
+ }
+
+ public int getMaxTextureSize() {
+ return mRenderer.getMaxTextureSize();
+ }
+
+ /** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
+ public IntBuffer getPixels() {
+ return mRenderer.getPixels();
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
new file mode 100644
index 000000000000..3514b4207461
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
@@ -0,0 +1,300 @@
+/* -*- 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) 2011-2012
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+* Chris Lord <chrislord.net@gmail.com>
+* Arkady Blyakher <rkadyb@mit.edu>
+*
+* 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 android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * Encapsulates the logic needed to draw a layer made of multiple tiles.
+ * <p/>
+ * TODO: Support repeating.
+ */
+public class MultiTileLayer extends Layer {
+ private static final String LOGTAG = "GeckoMultiTileLayer";
+
+ private final CairoImage mImage;
+ private final ArrayList<SubTile> mTiles;
+ private IntSize mTileSize;
+ private IntSize mBufferSize;
+
+ public MultiTileLayer(CairoImage image, IntSize tileSize) {
+ super();
+
+ mImage = image;
+ mTileSize = tileSize;
+ mBufferSize = new IntSize(0, 0);
+ mTiles = new ArrayList<SubTile>();
+ }
+
+ public void invalidate(Rect dirtyRect) {
+ if (!inTransaction()) {
+ throw new RuntimeException("invalidate() is only valid inside a transaction");
+ }
+
+ for (SubTile layer : mTiles) {
+ IntSize tileSize = layer.getSize();
+ Rect tileRect = new Rect(layer.x, layer.y, layer.x + tileSize.width, layer.y + tileSize.height);
+
+ if (tileRect.intersect(dirtyRect)) {
+ tileRect.offset(-layer.x, -layer.y);
+ layer.invalidate(tileRect);
+ }
+ }
+ }
+
+ public void invalidate() {
+ for (SubTile layer : mTiles) {
+ layer.invalidate();
+ }
+ }
+
+ @Override
+ public IntSize getSize() {
+ return mImage.getSize();
+ }
+
+ private void validateTiles() {
+ IntSize size = getSize();
+
+ if (size.equals(mBufferSize)) {
+ return;
+ }
+
+ // Regenerate tiles
+ mTiles.clear();
+ int offset = 0;
+ final int format = mImage.getFormat();
+ final ByteBuffer buffer = mImage.getBuffer().slice();
+ final int bpp = CairoUtils.bitsPerPixelForCairoFormat(format) / 8;
+ for (int y = 0; y < size.height; y += mTileSize.height) {
+ for (int x = 0; x < size.width; x += mTileSize.width) {
+ // Create a CairoImage implementation that returns a
+ // tile from the parent CairoImage. It's assumed that
+ // the tiles are stored in series.
+ final IntSize layerSize =
+ new IntSize(Math.min(mTileSize.width, size.width - x),
+ Math.min(mTileSize.height, size.height - y));
+ final int tileOffset = offset;
+
+ CairoImage subImage = new CairoImage() {
+ @Override
+ public ByteBuffer getBuffer() {
+ // Create a ByteBuffer that shares the data of the original
+ // buffer, but is positioned and limited so that only the
+ // tile data is accessible.
+ buffer.position(tileOffset);
+ ByteBuffer tileBuffer = buffer.slice();
+ tileBuffer.limit(layerSize.getArea() * bpp);
+
+ return tileBuffer;
+ }
+
+ @Override
+ public IntSize getSize() {
+ return layerSize;
+ }
+
+ @Override
+ public int getFormat() {
+ return format;
+ }
+ };
+
+ mTiles.add(new SubTile(subImage, x, y));
+ offset += layerSize.getArea() * bpp;
+ }
+ }
+
+ // Set tile origins and resolution
+ refreshTileMetrics(getOrigin(), getResolution(), false);
+
+ mBufferSize = size;
+ }
+
+ @Override
+ protected boolean performUpdates(RenderContext context) {
+ super.performUpdates(context);
+
+ validateTiles();
+
+ // Iterate over the tiles and decide which ones we'll be drawing
+ int dirtyTiles = 0;
+ boolean screenUpdateDone = false;
+ SubTile firstDirtyTile = null;
+ for (SubTile layer : mTiles) {
+ // First do a non-texture update to make sure coordinates are
+ // up-to-date.
+ boolean invalid = layer.getSkipTextureUpdate();
+ layer.setSkipTextureUpdate(true);
+ layer.performUpdates(context);
+
+ RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
+ boolean isDirty = layer.isDirty();
+
+ if (isDirty) {
+ if (!RectF.intersects(layerBounds, context.viewport)) {
+ if (firstDirtyTile == null)
+ firstDirtyTile = layer;
+ dirtyTiles++;
+ invalid = true;
+ } else {
+ // This tile intersects with the screen and is dirty,
+ // update it immediately.
+ layer.setSkipTextureUpdate(false);
+ screenUpdateDone = true;
+ layer.performUpdates(context);
+ invalid = false;
+ }
+ }
+
+ // We use the SkipTextureUpdate flag as a marker of a tile's
+ // validity. This is required, as sometimes layers are drawn
+ // without updating first, and we mustn't draw tiles that have
+ // been marked as invalid that we haven't updated.
+ layer.setSkipTextureUpdate(invalid);
+ }
+
+ // Now if no tiles that intersect with the screen were updated, update
+ // a single tile that doesn't (if there are any). This has the effect
+ // of spreading out non-critical texture upload over time, and smoothing
+ // upload-related hitches.
+ if (!screenUpdateDone && firstDirtyTile != null) {
+ firstDirtyTile.setSkipTextureUpdate(false);
+ firstDirtyTile.performUpdates(context);
+ dirtyTiles--;
+ }
+
+ return (dirtyTiles == 0);
+ }
+
+ private void refreshTileMetrics(Point origin, float resolution, boolean inTransaction) {
+ IntSize size = getSize();
+ for (SubTile layer : mTiles) {
+ if (!inTransaction) {
+ layer.beginTransaction();
+ }
+
+ if (origin != null) {
+ layer.setOrigin(new Point(origin.x + layer.x, origin.y + layer.y));
+ }
+ if (resolution >= 0.0f) {
+ layer.setResolution(resolution);
+ }
+
+ if (!inTransaction) {
+ layer.endTransaction();
+ }
+ }
+ }
+
+ @Override
+ public void setOrigin(Point newOrigin) {
+ super.setOrigin(newOrigin);
+ refreshTileMetrics(newOrigin, -1, true);
+ }
+
+ @Override
+ public void setResolution(float newResolution) {
+ super.setResolution(newResolution);
+ refreshTileMetrics(null, newResolution, true);
+ }
+
+ @Override
+ public void beginTransaction() {
+ super.beginTransaction();
+
+ for (SubTile layer : mTiles) {
+ layer.beginTransaction();
+ }
+ }
+
+ @Override
+ public void endTransaction() {
+ for (SubTile layer : mTiles) {
+ layer.endTransaction();
+ }
+
+ super.endTransaction();
+ }
+
+ @Override
+ public void draw(RenderContext context) {
+ for (SubTile layer : mTiles) {
+ // We use the SkipTextureUpdate flag as a validity flag. If it's false,
+ // the contents of this tile are invalid and we shouldn't draw it.
+ if (layer.getSkipTextureUpdate())
+ continue;
+
+ // Avoid work, only draw tiles that intersect with the viewport
+ RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
+ if (RectF.intersects(layerBounds, context.viewport))
+ layer.draw(context);
+ }
+ }
+
+ @Override
+ public Region getValidRegion(RenderContext context) {
+ Region validRegion = new Region();
+ for (SubTile tile : mTiles) {
+ if (tile.getSkipTextureUpdate())
+ continue;
+ validRegion.op(tile.getValidRegion(context), Region.Op.UNION);
+ }
+
+ return validRegion;
+ }
+
+ class SubTile extends SingleTileLayer {
+ public int x;
+ public int y;
+
+ public SubTile(CairoImage mImage, int mX, int mY) {
+ super(mImage);
+ x = mX;
+ y = mY;
+ }
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java
new file mode 100644
index 000000000000..f139b202c7b5
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java
@@ -0,0 +1,124 @@
+/* -*- 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.FloatSize;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.opengl.GLES11;
+import android.opengl.GLES11Ext;
+import android.util.Log;
+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 static final int PATCH_SIZE = 16;
+ private static final int TEXTURE_SIZE = 48;
+
+ public NinePatchTileLayer(CairoImage image) {
+ super(false, image);
+ }
+
+ @Override
+ public void draw(RenderContext context) {
+ if (!initialized())
+ return;
+
+ GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
+ GLES11.glEnable(GL10.GL_BLEND);
+ try {
+ GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
+ drawPatches(context);
+ } finally {
+ GLES11.glDisable(GL10.GL_BLEND);
+ }
+ }
+
+ private void drawPatches(RenderContext context) {
+ /*
+ * We divide the nine-patch bitmap up as follows:
+ *
+ * +---+---+---+
+ * | 0 | 1 | 2 |
+ * +---+---+---+
+ * | 3 | | 4 |
+ * +---+---+---+
+ * | 5 | 6 | 7 |
+ * +---+---+---+
+ */
+
+ FloatSize size = context.pageSize;
+ float width = size.width, height = size.height;
+
+ drawPatch(context, 0, 0, /* 0 */
+ 0.0f, 0.0f, PATCH_SIZE, PATCH_SIZE);
+ drawPatch(context, PATCH_SIZE, 0, /* 1 */
+ PATCH_SIZE, 0.0f, width, PATCH_SIZE);
+ drawPatch(context, PATCH_SIZE * 2, 0, /* 2 */
+ PATCH_SIZE + width, 0.0f, PATCH_SIZE, PATCH_SIZE);
+ drawPatch(context, 0, PATCH_SIZE, /* 3 */
+ 0.0f, PATCH_SIZE, PATCH_SIZE, height);
+ drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE, /* 4 */
+ PATCH_SIZE + width, PATCH_SIZE, PATCH_SIZE, height);
+ drawPatch(context, 0, PATCH_SIZE * 2, /* 5 */
+ 0.0f, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE);
+ drawPatch(context, PATCH_SIZE, PATCH_SIZE * 2, /* 6 */
+ PATCH_SIZE, PATCH_SIZE + height, width, PATCH_SIZE);
+ drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE * 2, /* 7 */
+ PATCH_SIZE + width, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE);
+ }
+
+ private void drawPatch(RenderContext context, int textureX, int textureY, float tileX,
+ float tileY, float tileWidth, float tileHeight) {
+ int[] cropRect = { textureX, textureY + PATCH_SIZE, PATCH_SIZE, -PATCH_SIZE };
+ GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
+ 0);
+
+ RectF viewport = context.viewport;
+ float viewportHeight = viewport.height();
+ float drawX = tileX - viewport.left - PATCH_SIZE;
+ float drawY = viewportHeight - (tileY + tileHeight - viewport.top - PATCH_SIZE);
+ GLES11Ext.glDrawTexfOES(drawX, drawY, 0.0f, tileWidth, tileHeight);
+ }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PanningPerfAPI.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PanningPerfAPI.java
new file mode 100644
index 000000000000..c9414ac8ecf0
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PanningPerfAPI.java
@@ -0,0 +1,125 @@
+/* -*- 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) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Kartikaya Gupta <kgupta@mozilla.com>
+ * Chris Lord <chrislord.net@gmail.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.util.ArrayList;
+import java.util.List;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class PanningPerfAPI {
+ private static final String LOGTAG = "GeckoPanningPerfAPI";
+
+ // make this large enough to avoid having to resize the frame time
+ // list, as that may be expensive and impact the thing we're trying
+ // to measure.
+ private static final int EXPECTED_FRAME_COUNT = 2048;
+
+ private static boolean mRecordingFrames = false;
+ private static List<Long> mFrameTimes;
+ private static long mFrameStartTime;
+
+ private static boolean mRecordingCheckerboard = false;
+ private static List<Float> mCheckerboardAmounts;
+ private static long mCheckerboardStartTime;
+
+ public static void startFrameTimeRecording() {
+ if (mRecordingFrames) {
+ Log.e(LOGTAG, "Error: startFrameTimeRecording() called while already recording!");
+ return;
+ }
+ mRecordingFrames = true;
+ if (mFrameTimes == null) {
+ mFrameTimes = new ArrayList<Long>(EXPECTED_FRAME_COUNT);
+ } else {
+ mFrameTimes.clear();
+ }
+ mFrameStartTime = SystemClock.uptimeMillis();
+ }
+
+ public static List<Long> stopFrameTimeRecording() {
+ if (!mRecordingFrames) {
+ Log.e(LOGTAG, "Error: stopFrameTimeRecording() called when not recording!");
+ return null;
+ }
+ mRecordingFrames = false;
+ return mFrameTimes;
+ }
+
+ public static void recordFrameTime() {
+ // this will be called often, so try to make it as quick as possible
+ if (mRecordingFrames) {
+ mFrameTimes.add(SystemClock.uptimeMillis() - mFrameStartTime);
+ }
+ }
+
+ public static boolean isRecordingCheckerboard() {
+ return mRecordingCheckerboard;
+ }
+
+ public static void startCheckerboardRecording() {
+ if (mRecordingCheckerboard) {
+ Log.e(LOGTAG, "Error: startCheckerboardRecording() called while already recording!");
+ return;
+ }
+ mRecordingCheckerboard = true;
+ if (mCheckerboardAmounts == null) {
+ mCheckerboardAmounts = new ArrayList<Float>(EXPECTED_FRAME_COUNT);
+ } else {
+ mCheckerboardAmounts.clear();
+ }
+ mCheckerboardStartTime = SystemClock.uptimeMillis();
+ }
+
+ public static List<Float> stopCheckerboardRecording() {
+ if (!mRecordingCheckerboard) {
+ Log.e(LOGTAG, "Error: stopCheckerboardRecording() called when not recording!");
+ return null;
+ }
+ mRecordingCheckerboard = false;
+ return mCheckerboardAmounts;
+ }
+
+ public static void recordCheckerboard(float amount) {
+ // this will be called often, so try to make it as quick as possible
+ if (mRecordingCheckerboard) {
+ mCheckerboardAmounts.add(amount);
+ }
+ }
+} \ No newline at end of file
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PointUtils.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PointUtils.java
new file mode 100644
index 000000000000..bff9f9f345a1
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PointUtils.java
@@ -0,0 +1,96 @@
+/* -*- 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) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Kartikaya Gupta <kgupta@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 android.graphics.Point;
+import android.graphics.PointF;
+import android.util.FloatMath;
+
+import org.json.JSONObject;
+import org.json.JSONException;
+import org.mozilla.gecko.util.FloatUtils;
+
+import java.lang.Math;
+
+public final class PointUtils {
+ public static PointF add(PointF one, PointF two) {
+ return new PointF(one.x + two.x, one.y + two.y);
+ }
+
+ public static PointF subtract(PointF one, PointF two) {
+ return new PointF(one.x - two.x, one.y - two.y);
+ }
+
+ public static PointF scale(PointF point, float factor) {
+ return new PointF(point.x * factor, point.y * factor);
+ }
+
+ public static Point round(PointF point) {
+ return new Point(Math.round(point.x), Math.round(point.y));
+ }
+
+ /* Returns a new point that is a linear interpolation between start and end points. weight conrols the weighting
+ * of each of the original points (weight = 1 returns endPoint, weight = 0 returns startPoint)
+ */
+ public static PointF interpolate(PointF startPoint, PointF endPoint, float weight) {
+ float x = FloatUtils.interpolate(startPoint.x, endPoint.x, weight);
+ float y = FloatUtils.interpolate(startPoint.y, endPoint.y, weight);
+ return new PointF(x, y);
+ }
+
+ /* Computes the magnitude of the given vector. */
+ public static float distance(PointF point) {
+ return (float)Math.sqrt(point.x * point.x + point.y * point.y);
+ }
+
+ /** Computes the scalar distance between two points. */
+ public static float distance(PointF one, PointF two) {
+ return PointF.length(one.x - two.x, one.y - two.y);
+ }
+
+ public static JSONObject toJSON(PointF point) throws JSONException {
+ // Ensure we put ints, not longs, because Gecko message handlers call getInt().
+ int x = Math.round(point.x);
+ int y = Math.round(point.y);
+ JSONObject json = new JSONObject();
+ json.put("x", x);
+ json.put("y", y);
+ return json;
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/RectUtils.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/RectUtils.java
new file mode 100644
index 000000000000..9a5049781c45
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/RectUtils.java
@@ -0,0 +1,139 @@
+/* -*- 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) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Kartikaya Gupta <kgupta@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.util.FloatUtils;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public final class RectUtils {
+ public static Rect create(JSONObject json) {
+ try {
+ int x = json.getInt("x");
+ int y = json.getInt("y");
+ int width = json.getInt("width");
+ int height = json.getInt("height");
+ return new Rect(x, y, x + width, y + height);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Rect contract(Rect rect, int lessWidth, int lessHeight) {
+ float halfLessWidth = lessWidth / 2.0f;
+ float halfLessHeight = lessHeight / 2.0f;
+ return new Rect(Math.round(rect.left + halfLessWidth),
+ Math.round(rect.top + halfLessHeight),
+ Math.round(rect.right - halfLessWidth),
+ Math.round(rect.bottom - halfLessHeight));
+ }
+
+ public static RectF contract(RectF rect, float lessWidth, float lessHeight) {
+ float halfLessWidth = lessWidth / 2;
+ float halfLessHeight = lessHeight / 2;
+ return new RectF(rect.left + halfLessWidth,
+ rect.top + halfLessHeight,
+ rect.right - halfLessWidth,
+ rect.bottom - halfLessHeight);
+ }
+
+ public static RectF expand(RectF rect, float moreWidth, float moreHeight) {
+ float halfMoreWidth = moreWidth / 2;
+ float halfMoreHeight = moreHeight / 2;
+ return new RectF(rect.left - halfMoreWidth,
+ rect.top - halfMoreHeight,
+ rect.right + halfMoreWidth,
+ rect.bottom + halfMoreHeight);
+ }
+
+ public static RectF intersect(RectF one, RectF two) {
+ float left = Math.max(one.left, two.left);
+ float top = Math.max(one.top, two.top);
+ float right = Math.min(one.right, two.right);
+ float bottom = Math.min(one.bottom, two.bottom);
+ return new RectF(left, top, Math.max(right, left), Math.max(bottom, top));
+ }
+
+ public static RectF scale(RectF rect, float scale) {
+ float x = rect.left * scale;
+ float y = rect.top * scale;
+ return new RectF(x, y,
+ x + (rect.width() * scale),
+ y + (rect.height() * scale));
+ }
+
+ /** Returns the nearest integer rect of the given rect. */
+ public static Rect round(RectF rect) {
+ return new Rect(Math.round(rect.left), Math.round(rect.top),
+ Math.round(rect.right), Math.round(rect.bottom));
+ }
+
+ public static IntSize getSize(Rect rect) {
+ return new IntSize(rect.width(), rect.height());
+ }
+
+ public static Point getOrigin(Rect rect) {
+ return new Point(rect.left, rect.top);
+ }
+
+ public static PointF getOrigin(RectF rect) {
+ return new PointF(rect.left, rect.top);
+ }
+
+ /*
+ * Returns the rect that represents a linear transition between `from` and `to` at time `t`,
+ * which is on the scale [0, 1).
+ */
+ public static RectF interpolate(RectF from, RectF to, float t) {
+ return new RectF(FloatUtils.interpolate(from.left, to.left, t),
+ FloatUtils.interpolate(from.top, to.top, t),
+ FloatUtils.interpolate(from.right, to.right, t),
+ FloatUtils.interpolate(from.bottom, to.bottom, t));
+ }
+
+ public static boolean fuzzyEquals(RectF a, RectF b) {
+ return FloatUtils.fuzzyEquals(a.top, b.top)
+ && FloatUtils.fuzzyEquals(a.left, b.left)
+ && FloatUtils.fuzzyEquals(a.right, b.right)
+ && FloatUtils.fuzzyEquals(a.bottom, b.bottom);
+ }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java
new file mode 100644
index 000000000000..68b7265b368b
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java
@@ -0,0 +1,425 @@
+/* -*- 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) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Kartikaya Gupta <kgupta@mozilla.com>
+ * Arkady Blyakher <rkadyb@mit.edu>
+ *
+ * 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 android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.opengl.GLES20;
+
+import org.libreoffice.LOKitShell;
+import org.mozilla.gecko.util.FloatUtils;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+/**
+ * Draws a small rect. This is scaled to become a scrollbar.
+ */
+public class ScrollbarLayer extends TileLayer {
+ public static final long FADE_DELAY = 500; // milliseconds before fade-out starts
+ private static final float FADE_AMOUNT = 0.03f; // how much (as a percent) the scrollbar should fade per frame
+
+ private static final int PADDING = 1; // gap between scrollbar and edge of viewport
+ private static final int BAR_SIZE = 6;
+ private static final int CAP_RADIUS = (BAR_SIZE / 2);
+ // Dimensions of the texture image
+ private static final float TEX_HEIGHT = 8.0f;
+ private static final float TEX_WIDTH = 8.0f;
+ // Texture coordinates for the scrollbar's body
+ // We take a 1x1 pixel from the center of the image and scale it to become the bar
+ private static final float[] BODY_TEX_COORDS = {
+ // x, y
+ CAP_RADIUS / TEX_WIDTH, CAP_RADIUS / TEX_HEIGHT,
+ CAP_RADIUS / TEX_WIDTH, (CAP_RADIUS + 1) / TEX_HEIGHT,
+ (CAP_RADIUS + 1) / TEX_WIDTH, CAP_RADIUS / TEX_HEIGHT,
+ (CAP_RADIUS + 1) / TEX_WIDTH, (CAP_RADIUS + 1) / TEX_HEIGHT
+ };
+ // Texture coordinates for the top cap of the scrollbar
+ private static final float[] TOP_CAP_TEX_COORDS = {
+ // x, y
+ 0, 1.0f - CAP_RADIUS / TEX_HEIGHT,
+ 0, 1.0f,
+ BAR_SIZE / TEX_WIDTH, 1.0f - CAP_RADIUS / TEX_HEIGHT,
+ BAR_SIZE / TEX_WIDTH, 1.0f
+ };
+ // Texture coordinates for the bottom cap of the scrollbar
+ private static final float[] BOT_CAP_TEX_COORDS = {
+ // x, y
+ 0, 1.0f - BAR_SIZE / TEX_HEIGHT,
+ 0, 1.0f - CAP_RADIUS / TEX_HEIGHT,
+ BAR_SIZE / TEX_WIDTH, 1.0f - BAR_SIZE / TEX_HEIGHT,
+ BAR_SIZE / TEX_WIDTH, 1.0f - CAP_RADIUS / TEX_HEIGHT
+ };
+ // Texture coordinates for the left cap of the scrollbar
+ private static final float[] LEFT_CAP_TEX_COORDS = {
+ // x, y
+ 0, 1.0f - BAR_SIZE / TEX_HEIGHT,
+ 0, 1.0f,
+ CAP_RADIUS / TEX_WIDTH, 1.0f - BAR_SIZE / TEX_HEIGHT,
+ CAP_RADIUS / TEX_WIDTH, 1.0f
+ };
+ // Texture coordinates for the right cap of the scrollbar
+ private static final float[] RIGHT_CAP_TEX_COORDS = {
+ // x, y
+ CAP_RADIUS / TEX_WIDTH, 1.0f - BAR_SIZE / TEX_HEIGHT,
+ CAP_RADIUS / TEX_WIDTH, 1.0f,
+ BAR_SIZE / TEX_WIDTH, 1.0f - BAR_SIZE / TEX_HEIGHT,
+ BAR_SIZE / TEX_WIDTH, 1.0f
+ };
+ private final boolean mVertical;
+ private final ByteBuffer mBuffer;
+ private final Bitmap mBitmap;
+ private final Canvas mCanvas;
+ private float mOpacity;
+ private boolean mFinalized = false;
+
+ private ScrollbarLayer(CairoImage image, boolean vertical, ByteBuffer buffer) {
+ super(false, image);
+ mVertical = vertical;
+ mBuffer = buffer;
+
+ IntSize size = image.getSize();
+ mBitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+ }
+
+ public static ScrollbarLayer create(boolean vertical) {
+ // just create an empty image for now, it will get drawn
+ // on demand anyway
+ int imageSize = IntSize.nextPowerOfTwo(BAR_SIZE);
+ ByteBuffer buffer = LOKitShell.allocateDirectBuffer(imageSize * imageSize * 4);
+ CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize,
+ CairoImage.FORMAT_ARGB32);
+ return new ScrollbarLayer(image, vertical, buffer);
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (!mFinalized && mBuffer != null)
+ LOKitShell.freeDirectBuffer(mBuffer);
+ mFinalized = true;
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Decrease the opacity of the scrollbar by one frame's worth.
+ * Return true if the opacity was decreased, or false if the scrollbars
+ * are already fully faded out.
+ */
+ public boolean fade() {
+ if (FloatUtils.fuzzyEquals(mOpacity, 0.0f)) {
+ return false;
+ }
+ beginTransaction();
+ try {
+ setOpacity(Math.max(mOpacity - FADE_AMOUNT, 0.0f));
+ invalidate();
+ } finally {
+ endTransaction();
+ }
+ return true;
+ }
+
+ /**
+ * Restore the opacity of the scrollbar to fully opaque.
+ * Return true if the opacity was changed, or false if the scrollbars
+ * are already fully opaque.
+ */
+ public boolean unfade() {
+ if (FloatUtils.fuzzyEquals(mOpacity, 1.0f)) {
+ return false;
+ }
+ beginTransaction();
+ try {
+ setOpacity(1.0f);
+ invalidate();
+ } finally {
+ endTransaction();
+ }
+ return true;
+ }
+
+ private void setOpacity(float opacity) {
+ mOpacity = opacity;
+
+ Paint foregroundPaint = new Paint();
+ foregroundPaint.setAntiAlias(true);
+ foregroundPaint.setStyle(Paint.Style.FILL);
+ // use a (a,r,g,b) color of (127,0,0,0), and multiply the alpha by mOpacity for fading
+ foregroundPaint.setColor(Color.argb(Math.round(mOpacity * 127), 0, 0, 0));
+
+ mCanvas.drawColor(Color.argb(0, 0, 0, 0), PorterDuff.Mode.CLEAR);
+ mCanvas.drawCircle(CAP_RADIUS, CAP_RADIUS, CAP_RADIUS, foregroundPaint);
+
+ mBitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
+ }
+
+ @Override
+ public void draw(RenderContext context) {
+ if (!initialized())
+ return;
+
+ try {
+ GLES20.glEnable(GLES20.GL_BLEND);
+ GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+
+ Rect rect = RectUtils.round(mVertical
+ ? getVerticalRect(context)
+ : getHorizontalRect(context));
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
+
+ float viewWidth = context.viewport.width();
+ float viewHeight = context.viewport.height();
+
+ float top = viewHeight - rect.top;
+ float bot = viewHeight - rect.bottom;
+
+ // Coordinates for the scrollbar's body combined with the texture coordinates
+ float[] bodyCoords = {
+ // x, y, z, texture_x, texture_y
+ rect.left / viewWidth, bot / viewHeight, 0,
+ BODY_TEX_COORDS[0], BODY_TEX_COORDS[1],
+
+ rect.left / viewWidth, (bot + rect.height()) / viewHeight, 0,
+ BODY_TEX_COORDS[2], BODY_TEX_COORDS[3],
+
+ (rect.left + rect.width()) / viewWidth, bot / viewHeight, 0,
+ BODY_TEX_COORDS[4], BODY_TEX_COORDS[5],
+
+ (rect.left + rect.width()) / viewWidth, (bot + rect.height()) / viewHeight, 0,
+ BODY_TEX_COORDS[6], BODY_TEX_COORDS[7]
+ };
+
+ // Get the buffer and handles from the context
+ FloatBuffer coordBuffer = context.coordBuffer;
+ int positionHandle = context.positionHandle;
+ int textureHandle = context.textureHandle;
+
+ // Make sure we are at position zero in the buffer in case other draw methods did not
+ // clean up after themselves
+ coordBuffer.position(0);
+ coordBuffer.put(bodyCoords);
+
+ // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+ coordBuffer.position(0);
+ GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
+ coordBuffer);
+
+ // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+ coordBuffer.position(3);
+ GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
+ coordBuffer);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+ // Reset the position in the buffer for the next set of vertex and texture coordinates.
+ coordBuffer.position(0);
+
+ if (mVertical) {
+ // top endcap
+ float[] topCap = {
+ // x, y, z, texture_x, texture_y
+ rect.left / viewWidth, top / viewHeight, 0,
+ TOP_CAP_TEX_COORDS[0], TOP_CAP_TEX_COORDS[1],
+
+ rect.left / viewWidth, (top + CAP_RADIUS) / viewHeight, 0,
+ TOP_CAP_TEX_COORDS[2], TOP_CAP_TEX_COORDS[3],
+
+ (rect.left + BAR_SIZE) / viewWidth, top / viewHeight, 0,
+ TOP_CAP_TEX_COORDS[4], TOP_CAP_TEX_COORDS[5],
+
+ (rect.left + BAR_SIZE) / viewWidth, (top + CAP_RADIUS) / viewHeight, 0,
+ TOP_CAP_TEX_COORDS[6], TOP_CAP_TEX_COORDS[7]
+ };
+
+ coordBuffer.put(topCap);
+
+ // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+ coordBuffer.position(0);
+ GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
+ coordBuffer);
+
+ // Texture coordinates are texture_x, texture_y starting at position 3 into the
+ // buffer.
+ coordBuffer.position(3);
+ GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
+ coordBuffer);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+ // Reset the position in the buffer for the next set of vertex and texture
+ // coordinates.
+ coordBuffer.position(0);
+
+ // bottom endcap
+ float[] botCap = {
+ // x, y, z, texture_x, texture_y
+ rect.left / viewWidth, (bot - CAP_RADIUS) / viewHeight, 0,
+ BOT_CAP_TEX_COORDS[0], BOT_CAP_TEX_COORDS[1],
+
+ rect.left / viewWidth, (bot) / viewHeight, 0,
+ BOT_CAP_TEX_COORDS[2], BOT_CAP_TEX_COORDS[3],
+
+ (rect.left + BAR_SIZE) / viewWidth, (bot - CAP_RADIUS) / viewHeight, 0,
+ BOT_CAP_TEX_COORDS[4], BOT_CAP_TEX_COORDS[5],
+
+ (rect.left + BAR_SIZE) / viewWidth, (bot) / viewHeight, 0,
+ BOT_CAP_TEX_COORDS[6], BOT_CAP_TEX_COORDS[7]
+ };
+
+ coordBuffer.put(botCap);
+
+ // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+ coordBuffer.position(0);
+ GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
+ coordBuffer);
+
+ // Texture coordinates are texture_x, texture_y starting at position 3 into the
+ // buffer.
+ coordBuffer.position(3);
+ GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
+ coordBuffer);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+ // Reset the position in the buffer for the next set of vertex and texture
+ // coordinates.
+ coordBuffer.position(0);
+ } else {
+ // left endcap
+ float[] leftCap = {
+ // x, y, z, texture_x, texture_y
+ (rect.left - CAP_RADIUS) / viewWidth, bot / viewHeight, 0,
+ LEFT_CAP_TEX_COORDS[0], LEFT_CAP_TEX_COORDS[1],
+ (rect.left - CAP_RADIUS) / viewWidth, (bot + BAR_SIZE) / viewHeight, 0,
+ LEFT_CAP_TEX_COORDS[2], LEFT_CAP_TEX_COORDS[3],
+ (rect.left) / viewWidth, bot / viewHeight, 0, LEFT_CAP_TEX_COORDS[4],
+ LEFT_CAP_TEX_COORDS[5],
+ (rect.left) / viewWidth, (bot + BAR_SIZE) / viewHeight, 0,
+ LEFT_CAP_TEX_COORDS[6], LEFT_CAP_TEX_COORDS[7]
+ };
+
+ coordBuffer.put(leftCap);
+
+ // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+ coordBuffer.position(0);
+ GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
+ coordBuffer);
+
+ // Texture coordinates are texture_x, texture_y starting at position 3 into the
+ // buffer.
+ coordBuffer.position(3);
+ GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
+ coordBuffer);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+ // Reset the position in the buffer for the next set of vertex and texture
+ // coordinates.
+ coordBuffer.position(0);
+
+ // right endcap
+ float[] rightCap = {
+ // x, y, z, texture_x, texture_y
+ rect.right / viewWidth, (bot) / viewHeight, 0,
+ RIGHT_CAP_TEX_COORDS[0], RIGHT_CAP_TEX_COORDS[1],
+
+ rect.right / viewWidth, (bot + BAR_SIZE) / viewHeight, 0,
+ RIGHT_CAP_TEX_COORDS[2], RIGHT_CAP_TEX_COORDS[3],
+
+ (rect.right + CAP_RADIUS) / viewWidth, (bot) / viewHeight, 0,
+ RIGHT_CAP_TEX_COORDS[4], RIGHT_CAP_TEX_COORDS[5],
+
+ (rect.right + CAP_RADIUS) / viewWidth, (bot + BAR_SIZE) / viewHeight, 0,
+ RIGHT_CAP_TEX_COORDS[6], RIGHT_CAP_TEX_COORDS[7]
+ };
+
+ coordBuffer.put(rightCap);
+
+ // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+ coordBuffer.position(0);
+ GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
+ coordBuffer);
+
+ // Texture coordinates are texture_x, texture_y starting at position 3 into the
+ // buffer.
+ coordBuffer.position(3);
+ GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
+ coordBuffer);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ }
+ } finally {
+ GLES20.glDisable(GLES20.GL_BLEND);
+ }
+ }
+
+ private RectF getVerticalRect(RenderContext context) {
+ RectF viewport = context.viewport;
+ FloatSize pageSize = context.pageSize;
+ float barStart = (viewport.height() * viewport.top / pageSize.height) + CAP_RADIUS;
+ float barEnd = (viewport.height() * viewport.bottom / pageSize.height) - CAP_RADIUS;
+ if (barStart > barEnd) {
+ float middle = (barStart + barEnd) / 2.0f;
+ barStart = barEnd = middle;
+ }
+ float right = viewport.width() - PADDING;
+ return new RectF(right - BAR_SIZE, barStart, right, barEnd);
+ }
+
+ private RectF getHorizontalRect(RenderContext context) {
+ RectF viewport = context.viewport;
+ FloatSize pageSize = context.pageSize;
+ float barStart = (viewport.width() * viewport.left / pageSize.width) + CAP_RADIUS;
+ float barEnd = (viewport.width() * viewport.right / pageSize.width) - CAP_RADIUS;
+ if (barStart > barEnd) {
+ float middle = (barStart + barEnd) / 2.0f;
+ barStart = barEnd = middle;
+ }
+ float bottom = viewport.height() - PADDING;
+ return new RectF(barStart, bottom - BAR_SIZE, barEnd, bottom);
+ }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java
new file mode 100644
index 000000000000..d18579e1a117
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java
@@ -0,0 +1,127 @@
+/* -*- 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>
+ * Arkady Blyakher <rkadyb@mit.edu>
+ *
+ * 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.graphics.PointF;
+import android.graphics.RectF;
+import android.opengl.GLES20;
+import android.util.Log;
+import java.nio.FloatBuffer;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Encapsulates the logic needed to draw a single textured tile.
+ *
+ * TODO: Repeating textures really should be their own type of layer.
+ */
+public class SingleTileLayer extends TileLayer {
+ public SingleTileLayer(CairoImage image) { this(false, image); }
+
+ public SingleTileLayer(boolean repeat, CairoImage image) {
+ super(repeat, image);
+ }
+
+ @Override
+ public void draw(RenderContext context) {
+ // mTextureIDs may be null here during startup if Layer.java's draw method
+ // failed to acquire the transaction lock and call performUpdates.
+ if (!initialized())
+ return;
+
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
+
+ RectF bounds;
+ int[] cropRect;
+ IntSize size = getSize();
+ RectF viewport = context.viewport;
+
+ if (repeats()) {
+ bounds = new RectF(0.0f, 0.0f, viewport.width(), viewport.height());
+ int width = Math.round(viewport.width());
+ int height = Math.round(viewport.height());
+ cropRect = new int[] { 0, 0, width, height };
+ } else {
+ bounds = getBounds(context, new FloatSize(size));
+ cropRect = new int[] { 0, 0, size.width, size.height };
+ }
+
+ float height = bounds.height();
+ float left = bounds.left - viewport.left;
+ float top = viewport.height() - (bounds.top + height - viewport.top);
+
+ float[] coords = {
+ //x, y, z, texture_x, texture_y
+ left/viewport.width(), top/viewport.height(), 0,
+ cropRect[0]/(float)size.width, cropRect[1]/(float)size.height,
+
+ left/viewport.width(), (top+height)/viewport.height(), 0,
+ cropRect[0]/(float)size.width, cropRect[3]/(float)size.height,
+
+ (left+bounds.width())/viewport.width(), top/viewport.height(), 0,
+ cropRect[2]/(float)size.width, cropRect[1]/(float)size.height,
+
+ (left+bounds.width())/viewport.width(), (top+height)/viewport.height(), 0,
+ cropRect[2]/(float)size.width, cropRect[3]/(float)size.height
+ };
+
+ FloatBuffer coordBuffer = context.coordBuffer;
+ int positionHandle = context.positionHandle;
+ int textureHandle = context.textureHandle;
+
+ // Make sure we are at position zero in the buffer in case other draw methods did not clean
+ // up after themselves
+ coordBuffer.position(0);
+ coordBuffer.put(coords);
+
+ // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+ coordBuffer.position(0);
+ GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
+
+ // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+ coordBuffer.position(3);
+ GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextLayer.java
new file mode 100644
index 000000000000..f2cc640baaa7
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextLayer.java
@@ -0,0 +1,117 @@
+/* -*- 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.GeckoAppShell;
+import org.libreoffice.LOKitShell;
+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 final ByteBuffer mBuffer;
+ private final IntSize mSize;
+ private boolean mFinalized = false;
+
+ /*
+ * This awkward pattern is necessary due to Java's restrictions on when one can call superclass
+ * constructors.
+ */
+ private TextLayer(ByteBuffer buffer, BufferedCairoImage image, IntSize size, String text) {
+ super(false, image);
+ mBuffer = buffer;
+ mSize = size;
+ renderText(text);
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (!mFinalized && mBuffer != null)
+ /*GeckoAppShell*/ LOKitShell.freeDirectBuffer(mBuffer);
+ mFinalized = true;
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public static TextLayer create(IntSize size, String text) {
+ ByteBuffer buffer = /*GeckoAppShell*/LOKitShell.allocateDirectBuffer(size.width * size.height * 4);
+ BufferedCairoImage image = new BufferedCairoImage(buffer, size.width, size.height,
+ CairoImage.FORMAT_ARGB32);
+ return new TextLayer(buffer, image, size, text);
+ }
+
+ public void setText(String text) {
+ renderText(text);
+ invalidate();
+ }
+
+ private void renderText(String text) {
+ 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(text) + 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(text, 6.0f, 18.0f, textPaint);
+
+ bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextureGenerator.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextureGenerator.java
new file mode 100644
index 000000000000..4392c55e1ae7
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextureGenerator.java
@@ -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):
+ * James Willcox <jwillcox@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 android.opengl.GLES10;
+import java.util.Stack;
+
+public class TextureGenerator {
+ private static final int MIN_TEXTURES = 5;
+
+ private static TextureGenerator sSharedInstance;
+ private Stack<Integer> mTextureIds;
+
+ private TextureGenerator() { mTextureIds = new Stack<Integer>(); }
+
+ public static TextureGenerator get() {
+ if (sSharedInstance == null)
+ sSharedInstance = new TextureGenerator();
+ return sSharedInstance;
+ }
+
+ public synchronized int take() {
+ if (mTextureIds.empty())
+ return 0;
+
+ return (int)mTextureIds.pop();
+ }
+
+ public synchronized void fill() {
+ int[] textures = new int[1];
+ while (mTextureIds.size() < MIN_TEXTURES) {
+ GLES10.glGenTextures(1, textures, 0);
+ mTextureIds.push(textures[0]);
+ }
+ }
+}
+
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextureReaper.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextureReaper.java
new file mode 100644
index 000000000000..e18139cb9bc9
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextureReaper.java
@@ -0,0 +1,76 @@
+/* -*- 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>
+ * Arkady Blyakher <rkadyb@mit.edu>
+ *
+ * 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 android.opengl.GLES20;
+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)
+ add(textureID);
+ }
+
+ public void add(int textureID) {
+ mDeadTextureIDs.add(textureID);
+ }
+
+ public void reap() {
+ int[] deadTextureIDs = new int[mDeadTextureIDs.size()];
+ for (int i = 0; i < deadTextureIDs.length; i++)
+ deadTextureIDs[i] = mDeadTextureIDs.get(i);
+ mDeadTextureIDs.clear();
+
+ GLES20.glDeleteTextures(deadTextureIDs.length, deadTextureIDs, 0);
+ }
+}
+
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TileLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TileLayer.java
new file mode 100644
index 000000000000..64ec94dc694a
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TileLayer.java
@@ -0,0 +1,256 @@
+/* -*- 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>
+ * Arkady Blyakher <rkadyb@mit.edu>
+ *
+ * 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 android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.opengl.GLES20;
+import android.util.Log;
+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 static final String LOGTAG = "GeckoTileLayer";
+
+ private final Rect mDirtyRect;
+ private final CairoImage mImage;
+ private final boolean mRepeat;
+ private IntSize mSize;
+ private boolean mSkipTextureUpdate;
+ private int[] mTextureIDs;
+
+ public TileLayer(boolean repeat, CairoImage image) {
+ mRepeat = repeat;
+ mImage = image;
+ mSize = new IntSize(0, 0);
+ mSkipTextureUpdate = false;
+
+ IntSize bufferSize = mImage.getSize();
+ mDirtyRect = new Rect();
+ }
+
+ @Override
+ public IntSize getSize() { return mImage.getSize(); }
+
+ protected boolean repeats() { return mRepeat; }
+ protected int getTextureID() { return mTextureIDs[0]; }
+ protected boolean initialized() { return mImage != null && mTextureIDs != null; }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mTextureIDs != null)
+ TextureReaper.get().add(mTextureIDs);
+ }
+
+ /**
+ * Invalidates the given rect so that it will be uploaded again. Only valid inside a
+ * transaction.
+ */
+ public void invalidate(Rect rect) {
+ if (!inTransaction())
+ throw new RuntimeException("invalidate() is only valid inside a transaction");
+ mDirtyRect.union(rect);
+ }
+
+ public void invalidate() {
+ IntSize bufferSize = mImage.getSize();
+ invalidate(new Rect(0, 0, bufferSize.width, bufferSize.height));
+ }
+
+ public boolean isDirty() {
+ return mImage.getSize().isPositive() && (mTextureIDs == null || !mDirtyRect.isEmpty());
+ }
+
+ private void validateTexture() {
+ /* Calculate the ideal texture size. This must be a power of two if
+ * the texture is repeated or OpenGL ES 2.0 isn't supported, as
+ * OpenGL ES 2.0 is required for NPOT texture support (without
+ * extensions), but doesn't support repeating NPOT textures.
+ *
+ * XXX Currently, we don't pick a GLES 2.0 context, so always round.
+ */
+ IntSize bufferSize = mImage.getSize();
+ IntSize textureSize = bufferSize;
+
+ textureSize = bufferSize.nextPowerOfTwo();
+
+ if (!textureSize.equals(mSize)) {
+ mSize = textureSize;
+
+ // Delete the old texture
+ if (mTextureIDs != null) {
+ TextureReaper.get().add(mTextureIDs);
+ mTextureIDs = null;
+
+ // Free the texture immediately, so we don't incur a
+ // temporarily increased memory usage.
+ TextureReaper.get().reap();
+ }
+ }
+ }
+
+ /** Tells the tile not to update the texture on the next update. */
+ public void setSkipTextureUpdate(boolean skip) {
+ mSkipTextureUpdate = skip;
+ }
+
+ public boolean getSkipTextureUpdate() {
+ return mSkipTextureUpdate;
+ }
+
+ @Override
+ protected boolean performUpdates(RenderContext context) {
+ super.performUpdates(context);
+
+ if (mSkipTextureUpdate) {
+ return false;
+ }
+
+ // Reallocate the texture if the size has changed
+ validateTexture();
+
+ // Don't do any work if the image has an invalid size.
+ if (!mImage.getSize().isPositive())
+ return true;
+
+ // If we haven't allocated a texture, assume the whole region is dirty
+ if (mTextureIDs == null) {
+ uploadFullTexture();
+ } else {
+ uploadDirtyRect(mDirtyRect);
+ }
+
+ mDirtyRect.setEmpty();
+
+ return true;
+ }
+
+ private void uploadFullTexture() {
+ IntSize bufferSize = mImage.getSize();
+ uploadDirtyRect(new Rect(0, 0, bufferSize.width, bufferSize.height));
+ }
+
+ private void uploadDirtyRect(Rect dirtyRect) {
+ // If we have nothing to upload, just return for now
+ if (dirtyRect.isEmpty())
+ return;
+
+ // It's possible that the buffer will be null, check for that and return
+ ByteBuffer imageBuffer = mImage.getBuffer();
+ if (imageBuffer == null)
+ return;
+
+ boolean newlyCreated = false;
+
+ if (mTextureIDs == null) {
+ mTextureIDs = new int[1];
+ GLES20.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
+ newlyCreated = true;
+ }
+
+ IntSize bufferSize = mImage.getSize();
+ Rect bufferRect = new Rect(0, 0, bufferSize.width, bufferSize.height);
+
+ int cairoFormat = mImage.getFormat();
+ CairoGLInfo glInfo = new CairoGLInfo(cairoFormat);
+
+ bindAndSetGLParameters();
+
+ if (newlyCreated || dirtyRect.contains(bufferRect)) {
+ if (mSize.equals(bufferSize)) {
+ GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width,
+ mSize.height, 0, glInfo.format, glInfo.type, imageBuffer);
+ return;
+ } else {
+ GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width,
+ mSize.height, 0, glInfo.format, glInfo.type, null);
+ GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, bufferSize.width,
+ bufferSize.height, glInfo.format, glInfo.type, imageBuffer);
+ return;
+ }
+ }
+
+ // Make sure that the dirty region intersects with the buffer rect,
+ // otherwise we'll end up with an invalid buffer pointer.
+ if (!Rect.intersects(dirtyRect, bufferRect)) {
+ return;
+ }
+
+ /*
+ * Upload 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.
+ *
+ * XXX We should still use GL_EXT_unpack_subimage when available.
+ */
+ Buffer viewBuffer = imageBuffer.slice();
+ int bpp = CairoUtils.bitsPerPixelForCairoFormat(cairoFormat) / 8;
+ int position = dirtyRect.top * bufferSize.width * bpp;
+ if (position > viewBuffer.limit()) {
+ Log.e(LOGTAG, "### Position outside tile! " + dirtyRect.top);
+ return;
+ }
+
+ viewBuffer.position(position);
+ GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width,
+ Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()),
+ glInfo.format, glInfo.type, viewBuffer);
+ }
+
+ private void bindAndSetGLParameters() {
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
+ GLES20.GL_NEAREST);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
+ GLES20.GL_LINEAR);
+
+ int repeatMode = mRepeat ? GLES20.GL_REPEAT : GLES20.GL_CLAMP_TO_EDGE;
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, repeatMode);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, repeatMode);
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewTransform.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewTransform.java
new file mode 100644
index 000000000000..9f443ea52894
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewTransform.java
@@ -0,0 +1,51 @@
+/* -*- 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 ViewTransform {
+ public float x;
+ public float y;
+ public float scale;
+
+ public ViewTransform(float inX, float inY, float inScale) {
+ x = inX;
+ y = inY;
+ scale = inScale;
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java
new file mode 100644
index 000000000000..d98b47421520
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java
@@ -0,0 +1,306 @@
+/* -*- 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>
+ * Chris Lord <chrislord.net@gmail.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 android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.DisplayMetrics;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.libreoffice.LibreOfficeMainActivity;
+import org.mozilla.gecko.util.FloatUtils;
+
+/**
+ * ViewportMetrics manages state and contains some utility functions related to
+ * the page viewport for the Gecko layer client to use.
+ */
+public class ViewportMetrics {
+ private static final String LOGTAG = "GeckoViewportMetrics";
+ private static final float MAX_BIAS = 0.8f;
+ private FloatSize mPageSize;
+ private RectF mViewportRect;
+ private PointF mViewportOffset;
+ private float mZoomFactor;
+ // A scale from -1,-1 to 1,1 that represents what edge of the displayport
+ // we want the viewport to be biased towards.
+ private PointF mViewportBias;
+
+ public ViewportMetrics() {
+ DisplayMetrics metrics = new DisplayMetrics();
+ LibreOfficeMainActivity.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+ mPageSize = new FloatSize(metrics.widthPixels, metrics.heightPixels);
+ mViewportRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels);
+ mViewportOffset = new PointF(0, 0);
+ mZoomFactor = 1.0f;
+ mViewportBias = new PointF(0.0f, 0.0f);
+ }
+
+ public ViewportMetrics(ViewportMetrics viewport) {
+ mPageSize = new FloatSize(viewport.getPageSize());
+ mViewportRect = new RectF(viewport.getViewport());
+ PointF offset = viewport.getViewportOffset();
+ mViewportOffset = new PointF(offset.x, offset.y);
+ mZoomFactor = viewport.getZoomFactor();
+ mViewportBias = viewport.mViewportBias;
+ }
+
+ public ViewportMetrics(JSONObject json) throws JSONException {
+ float x = (float) json.getDouble("x");
+ float y = (float) json.getDouble("y");
+ float width = (float) json.getDouble("width");
+ float height = (float) json.getDouble("height");
+ float pageWidth = (float) json.getDouble("pageWidth");
+ float pageHeight = (float) json.getDouble("pageHeight");
+ float offsetX = (float) json.getDouble("offsetX");
+ float offsetY = (float) json.getDouble("offsetY");
+ float zoom = (float) json.getDouble("zoom");
+
+ mPageSize = new FloatSize(pageWidth, pageHeight);
+ mViewportRect = new RectF(x, y, x + width, y + height);
+ mViewportOffset = new PointF(offsetX, offsetY);
+ mZoomFactor = zoom;
+ mViewportBias = new PointF(0.0f, 0.0f);
+ }
+
+ public PointF getOptimumViewportOffset(IntSize displayportSize) {
+ /* XXX Until bug #524925 is fixed, changing the viewport origin will
+ * cause unnecessary relayouts. This may cause rendering time to
+ * increase and should be considered.
+ */
+ RectF viewport = getClampedViewport();
+
+ FloatSize bufferSpace = new FloatSize(displayportSize.width - viewport.width(),
+ displayportSize.height - viewport.height());
+ PointF optimumOffset =
+ new PointF(bufferSpace.width * ((mViewportBias.x + 1.0f) / 2.0f),
+ bufferSpace.height * ((mViewportBias.y + 1.0f) / 2.0f));
+
+ // Make sure this offset won't cause wasted pixels in the displayport
+ // (i.e. make sure the resultant displayport intersects with the page
+ // as much as possible)
+ if (viewport.left - optimumOffset.x < 0)
+ optimumOffset.x = viewport.left;
+ else if ((bufferSpace.width - optimumOffset.x) + viewport.right > mPageSize.width)
+ optimumOffset.x = bufferSpace.width - (mPageSize.width - viewport.right);
+
+ if (viewport.top - optimumOffset.y < 0)
+ optimumOffset.y = viewport.top;
+ else if ((bufferSpace.height - optimumOffset.y) + viewport.bottom > mPageSize.height)
+ optimumOffset.y = bufferSpace.height - (mPageSize.height - viewport.bottom);
+
+ return new PointF(Math.round(optimumOffset.x), Math.round(optimumOffset.y));
+ }
+
+ public PointF getOrigin() {
+ return new PointF(mViewportRect.left, mViewportRect.top);
+ }
+
+ public void setOrigin(PointF origin) {
+ // When the origin is set, we compare it with the last value set and
+ // change the viewport bias accordingly, so that any viewport based
+ // on these metrics will have a larger buffer in the direction of
+ // movement.
+
+ // XXX Note the comment about bug #524925 in getOptimumViewportOffset.
+ // Ideally, the viewport bias would be a sliding scale, but we
+ // don't want to change it too often at the moment.
+ if (FloatUtils.fuzzyEquals(origin.x, mViewportRect.left))
+ mViewportBias.x = 0;
+ else
+ mViewportBias.x = ((mViewportRect.left - origin.x) > 0) ? MAX_BIAS : -MAX_BIAS;
+ if (FloatUtils.fuzzyEquals(origin.y, mViewportRect.top))
+ mViewportBias.y = 0;
+ else
+ mViewportBias.y = ((mViewportRect.top - origin.y) > 0) ? MAX_BIAS : -MAX_BIAS;
+
+ mViewportRect.set(origin.x, origin.y,
+ origin.x + mViewportRect.width(),
+ origin.y + mViewportRect.height());
+ }
+
+ public PointF getDisplayportOrigin() {
+ return new PointF(mViewportRect.left - mViewportOffset.x,
+ mViewportRect.top - mViewportOffset.y);
+ }
+
+ public FloatSize getSize() {
+ return new FloatSize(mViewportRect.width(), mViewportRect.height());
+ }
+
+ public void setSize(FloatSize size) {
+ mViewportRect.right = mViewportRect.left + size.width;
+ mViewportRect.bottom = mViewportRect.top + size.height;
+ }
+
+ public RectF getViewport() {
+ return mViewportRect;
+ }
+
+ public void setViewport(RectF viewport) {
+ mViewportRect = viewport;
+ }
+
+ /**
+ * Returns the viewport rectangle, clamped within the page-size.
+ */
+ public RectF getClampedViewport() {
+ RectF clampedViewport = new RectF(mViewportRect);
+
+ // While the viewport size ought to never exceed the page size, we
+ // do the clamping in this order to make sure that the origin is
+ // never negative.
+ if (clampedViewport.right > mPageSize.width)
+ clampedViewport.offset(mPageSize.width - clampedViewport.right, 0);
+ if (clampedViewport.left < 0)
+ clampedViewport.offset(-clampedViewport.left, 0);
+
+ if (clampedViewport.bottom > mPageSize.height)
+ clampedViewport.offset(0, mPageSize.height - clampedViewport.bottom);
+ if (clampedViewport.top < 0)
+ clampedViewport.offset(0, -clampedViewport.top);
+
+ return clampedViewport;
+ }
+
+ public PointF getViewportOffset() {
+ return mViewportOffset;
+ }
+
+ public void setViewportOffset(PointF offset) {
+ mViewportOffset = offset;
+ }
+
+ public FloatSize getPageSize() {
+ return mPageSize;
+ }
+
+ public void setPageSize(FloatSize pageSize) {
+ mPageSize = pageSize;
+ }
+
+ public float getZoomFactor() {
+ return mZoomFactor;
+ }
+
+ public void setZoomFactor(float zoomFactor) {
+ mZoomFactor = zoomFactor;
+ }
+
+ /* This will set the zoom factor and re-scale page-size and viewport offset
+ * accordingly. The given focus will remain at the same point on the screen
+ * after scaling.
+ */
+ public void scaleTo(float newZoomFactor, PointF focus) {
+ float scaleFactor = newZoomFactor / mZoomFactor;
+
+ mPageSize = mPageSize.scale(scaleFactor);
+
+ PointF origin = getOrigin();
+ origin.offset(focus.x, focus.y);
+ origin = PointUtils.scale(origin, scaleFactor);
+ origin.offset(-focus.x, -focus.y);
+ setOrigin(origin);
+
+ mZoomFactor = newZoomFactor;
+
+ // Similar to setOrigin, set the viewport bias based on the focal point
+ // of the zoom so that a viewport based on these metrics will have a
+ // larger buffer based on the direction of movement when scaling.
+ //
+ // This is biased towards scaling outwards, as zooming in doesn't
+ // really require a viewport bias.
+ mViewportBias.set(((focus.x / mViewportRect.width()) * (2.0f * MAX_BIAS)) - MAX_BIAS,
+ ((focus.y / mViewportRect.height()) * (2.0f * MAX_BIAS)) - MAX_BIAS);
+ }
+
+ /*
+ * Returns the viewport metrics that represent a linear transition between `from` and `to` at
+ * time `t`, which is on the scale [0, 1). This function interpolates the viewport rect, the
+ * page size, the offset, and the zoom factor.
+ */
+ public ViewportMetrics interpolate(ViewportMetrics to, float t) {
+ ViewportMetrics result = new ViewportMetrics();
+ result.mPageSize = mPageSize.interpolate(to.mPageSize, t);
+ result.mZoomFactor = FloatUtils.interpolate(mZoomFactor, to.mZoomFactor, t);
+ result.mViewportRect = RectUtils.interpolate(mViewportRect, to.mViewportRect, t);
+ result.mViewportOffset = PointUtils.interpolate(mViewportOffset, to.mViewportOffset, t);
+ return result;
+ }
+
+ public boolean fuzzyEquals(ViewportMetrics other) {
+ return mPageSize.fuzzyEquals(other.mPageSize)
+ && RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect)
+ && FloatUtils.fuzzyEquals(mViewportOffset, other.mViewportOffset)
+ && FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor);
+ }
+
+ public String toJSON() {
+ // Round off height and width. Since the height and width are the size of the screen, it
+ // makes no sense to send non-integer coordinates to Gecko.
+ int height = Math.round(mViewportRect.height());
+ int width = Math.round(mViewportRect.width());
+
+ StringBuffer sb = new StringBuffer(256);
+ sb.append("{ \"x\" : ").append(mViewportRect.left)
+ .append(", \"y\" : ").append(mViewportRect.top)
+ .append(", \"width\" : ").append(width)
+ .append(", \"height\" : ").append(height)
+ .append(", \"pageWidth\" : ").append(mPageSize.width)
+ .append(", \"pageHeight\" : ").append(mPageSize.height)
+ .append(", \"offsetX\" : ").append(mViewportOffset.x)
+ .append(", \"offsetY\" : ").append(mViewportOffset.y)
+ .append(", \"zoom\" : ").append(mZoomFactor)
+ .append(" }");
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buff = new StringBuffer(128);
+ buff.append("v=").append(mViewportRect.toString())
+ .append(" p=").append(mPageSize.toString())
+ .append(" z=").append(mZoomFactor)
+ .append(" o=").append(mViewportOffset.x)
+ .append(',').append(mViewportOffset.y);
+ return buff.toString();
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/VirtualLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/VirtualLayer.java
new file mode 100644
index 000000000000..0f314ae71620
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/VirtualLayer.java
@@ -0,0 +1,80 @@
+/* -*- 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>
+ * Chris Lord <chrislord.net@gmail.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 android.graphics.Point;
+
+public class VirtualLayer extends Layer {
+ private Listener mListener;
+ private IntSize mSize;
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void draw(RenderContext context) {
+ // No-op.
+ }
+
+ @Override
+ public IntSize getSize() {
+ return mSize;
+ }
+
+ public void setSize(IntSize size) {
+ mSize = size;
+ }
+
+ @Override
+ protected boolean performUpdates(RenderContext context) {
+ boolean dimensionsChanged = dimensionChangesPending();
+ boolean result = super.performUpdates(context);
+ if (dimensionsChanged && mListener != null) {
+ mListener.dimensionsChanged(getOrigin(), getResolution());
+ }
+
+ return result;
+ }
+
+ public interface Listener {
+ void dimensionsChanged(Point newOrigin, float newResolution);
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/WidgetTileLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/WidgetTileLayer.java
new file mode 100644
index 000000000000..b123d55c403d
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/WidgetTileLayer.java
@@ -0,0 +1,160 @@
+/* -*- 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):
+ * James Willcox <jwillcox@mozilla.com>
+ * Arkady Blyakher <rkadyb@mit.edu>
+ *
+ * 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.libreoffice.LOKitShell;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.SingleTileLayer;
+//import org.mozilla.gecko.GeckoAppShell;
+import android.graphics.RectF;
+import android.util.Log;
+import android.opengl.GLES20;
+import java.nio.FloatBuffer;
+
+/**
+ * Encapsulates the logic needed to draw the single-tiled Gecko texture
+ */
+public class WidgetTileLayer extends Layer {
+ private static final String LOGTAG = "WidgetTileLayer";
+
+ private int[] mTextureIDs;
+ private CairoImage mImage;
+
+ public WidgetTileLayer(CairoImage image) {
+ mImage = image;
+ }
+
+ protected boolean initialized() { return mTextureIDs != null; }
+
+ @Override
+ public IntSize getSize() { return mImage.getSize(); }
+
+ protected void bindAndSetGLParameters() {
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mTextureIDs != null)
+ TextureReaper.get().add(mTextureIDs);
+ }
+
+ @Override
+ protected boolean performUpdates(RenderContext context) {
+ super.performUpdates(context);
+
+ if (mTextureIDs == null) {
+ mTextureIDs = new int[1];
+ GLES20.glGenTextures(1, mTextureIDs, 0);
+ }
+
+ bindAndSetGLParameters();
+ LOKitShell.bindWidgetTexture();
+
+ return true;
+ }
+
+ @Override
+ public void draw(RenderContext context) {
+ // mTextureIDs may be null here during startup if Layer.java's draw method
+ // failed to acquire the transaction lock and call performUpdates.
+ if (!initialized())
+ return;
+
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
+
+ RectF bounds;
+ int[] cropRect;
+ IntSize size = getSize();
+ RectF viewport = context.viewport;
+
+ bounds = getBounds(context, new FloatSize(size));
+ cropRect = new int[] { 0, 0, size.width, size.height };
+ bounds.offset(-viewport.left, -viewport.top);
+
+ float top = viewport.height() - (bounds.top + bounds.height());
+
+ // There may be errors from a previous GL call, so clear them first because
+ // we want to check for one below
+ while (GLES20.glGetError() != GLES20.GL_NO_ERROR);
+
+ float[] coords = {
+ //x, y, z, texture_x, texture_y
+ bounds.left/viewport.width(), top/viewport.height(), 0,
+ cropRect[0]/size.width, cropRect[1]/size.height,
+
+ bounds.left/viewport.width(), (top+bounds.height())/viewport.height(), 0,
+ cropRect[0]/size.width, cropRect[3]/size.height,
+
+ (bounds.left+bounds.width())/viewport.width(), top/viewport.height(), 0,
+ cropRect[2]/size.width, cropRect[1]/size.height,
+
+ (bounds.left+bounds.width())/viewport.width(), (top+bounds.height())/viewport.height(),
+ 0,
+ cropRect[2]/size.width, cropRect[3]/size.height
+ };
+
+ // Get the buffer and handles from the context
+ FloatBuffer coordBuffer = context.coordBuffer;
+ int positionHandle = context.positionHandle;
+ int textureHandle = context.textureHandle;
+
+ // Make sure we are at position zero in the buffer in case other draw methods did not clean
+ // up after themselves
+ coordBuffer.position(0);
+ coordBuffer.put(coords);
+
+ // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+ coordBuffer.position(0);
+ GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
+
+ // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+ coordBuffer.position(3);
+ GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+ int error = GLES20.glGetError();
+ if (error != GLES20.GL_NO_ERROR) {
+ Log.i(LOGTAG, "Failed to draw texture: " + error);
+ }
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
new file mode 100644
index 000000000000..8c0fce4c73b3
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
@@ -0,0 +1,271 @@
+/* -*- 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) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Patrick Walton <pcwalton@mozilla.com>
+ * Kartikaya Gupta <kgupta@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.util.FloatUtils;
+
+/**
+ * This class represents the physics for one axis of movement (i.e. either
+ * horizontal or vertical). It tracks the different properties of movement
+ * like displacement, velocity, viewport dimensions, etc. pertaining to
+ * a particular axis.
+ */
+abstract class Axis {
+ // This fraction of velocity remains after every animation frame when the velocity is low.
+ private static final float FRICTION_SLOW = 0.85f;
+ // This fraction of velocity remains after every animation frame when the velocity is high.
+ private static final float FRICTION_FAST = 0.97f;
+ // Below this velocity (in pixels per frame), the friction starts increasing from FRICTION_FAST
+ // to FRICTION_SLOW.
+ private static final float VELOCITY_THRESHOLD = 10.0f;
+ // The maximum velocity change factor between events, per ms, in %.
+ // Direction changes are excluded.
+ private static final float MAX_EVENT_ACCELERATION = 0.012f;
+
+ // The rate of deceleration when the surface has overscrolled.
+ private static final float OVERSCROLL_DECEL_RATE = 0.04f;
+ // The percentage of the surface which can be overscrolled before it must snap back.
+ private static final float SNAP_LIMIT = 0.75f;
+
+ // The minimum amount of space that must be present for an axis to be considered scrollable,
+ // in pixels.
+ private static final float MIN_SCROLLABLE_DISTANCE = 0.5f;
+ // The number of milliseconds per frame assuming 60 fps
+ private static final float MS_PER_FRAME = 1000.0f / 60.0f;
+
+ private enum FlingStates {
+ STOPPED,
+ PANNING,
+ FLINGING,
+ }
+
+ private 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)
+ }
+
+ private final SubdocumentScrollHelper mSubscroller;
+
+ private float mFirstTouchPos; /* Position of the first touch event on the current drag. */
+ private float mTouchPos; /* Position of the most recent touch event on the current drag. */
+ private float mLastTouchPos; /* Position of the touch event before touchPos. */
+ private float mVelocity; /* Velocity in this direction; pixels per animation frame. */
+ public boolean mScrollingDisabled; /* Whether movement on this axis is locked. */
+ private boolean mDisableSnap; /* Whether overscroll snapping is disabled. */
+ private float mDisplacement;
+
+ private FlingStates mFlingState; /* The fling state we're in on this axis. */
+
+ protected abstract float getOrigin();
+ protected abstract float getViewportLength();
+ protected abstract float getPageLength();
+
+ Axis(SubdocumentScrollHelper subscroller) {
+ mSubscroller = subscroller;
+ }
+
+ private float getViewportEnd() {
+ return getOrigin() + getViewportLength();
+ }
+
+ void startTouch(float pos) {
+ mVelocity = 0.0f;
+ mScrollingDisabled = false;
+ mFirstTouchPos = mTouchPos = mLastTouchPos = pos;
+ }
+
+ float panDistance(float currentPos) {
+ return currentPos - mFirstTouchPos;
+ }
+
+ void setScrollingDisabled(boolean disabled) {
+ mScrollingDisabled = disabled;
+ }
+
+ void saveTouchPos() {
+ mLastTouchPos = mTouchPos;
+ }
+
+ void updateWithTouchAt(float pos, float timeDelta) {
+ float newVelocity = (mTouchPos - pos) / timeDelta * MS_PER_FRAME;
+
+ // If there's a direction change, or current velocity is very low,
+ // allow setting of the velocity outright. Otherwise, use the current
+ // velocity and a maximum change factor to set the new velocity.
+ boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f;
+ boolean directionChange = (mVelocity > 0) != (newVelocity > 0);
+ if (curVelocityIsLow || (directionChange && !FloatUtils.fuzzyEquals(newVelocity, 0.0f))) {
+ mVelocity = newVelocity;
+ } else {
+ float maxChange = Math.abs(mVelocity * timeDelta * MAX_EVENT_ACCELERATION);
+ mVelocity = Math.min(mVelocity + maxChange, Math.max(mVelocity - maxChange, newVelocity));
+ }
+
+ mTouchPos = pos;
+ }
+
+ boolean overscrolled() {
+ return getOverscroll() != Overscroll.NONE;
+ }
+
+ private Overscroll getOverscroll() {
+ boolean minus = (getOrigin() < 0.0f);
+ boolean plus = (getViewportEnd() > getPageLength());
+ 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 -getOrigin();
+ case PLUS: return getViewportEnd() - getPageLength();
+ case BOTH: return getViewportEnd() - getPageLength() - getOrigin();
+ default: return 0.0f;
+ }
+ }
+
+ /*
+ * Returns true if the page is zoomed in to some degree along this axis such that scrolling is
+ * possible and this axis has not been scroll locked while panning. Otherwise, returns false.
+ */
+ private boolean scrollable() {
+ return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE &&
+ !mScrollingDisabled;
+ }
+
+ /*
+ * Returns the resistance, as a multiplier, that should be taken into account when
+ * tracking or pinching.
+ */
+ float getEdgeResistance() {
+ float excess = getExcess();
+ if (excess > 0.0f) {
+ // excess can be greater than viewport length, but the resistance
+ // must never drop below 0.0
+ return Math.max(0.0f, SNAP_LIMIT - excess / getViewportLength());
+ }
+ return 1.0f;
+ }
+
+ /* Returns the velocity. If the axis is locked, returns 0. */
+ float getRealVelocity() {
+ return scrollable() ? mVelocity : 0f;
+ }
+
+ void startPan() {
+ mFlingState = FlingStates.PANNING;
+ }
+
+ void startFling(boolean stopped) {
+ mDisableSnap = mSubscroller.scrolling();
+
+ if (stopped) {
+ mFlingState = FlingStates.STOPPED;
+ } else {
+ mFlingState = FlingStates.FLINGING;
+ }
+ }
+
+ /* Advances a fling animation by one step. */
+ boolean advanceFling() {
+ if (mFlingState != FlingStates.FLINGING) {
+ return false;
+ }
+ if (mSubscroller.scrolling() && !mSubscroller.lastScrollSucceeded()) {
+ // if the subdocument stopped scrolling, it's because it reached the end
+ // of the subdocument. we don't do overscroll on subdocuments, so there's
+ // no point in continuing this fling.
+ return false;
+ }
+
+ float excess = getExcess();
+ if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f)) {
+ // If we aren't overscrolled, just apply friction.
+ if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) {
+ mVelocity *= FRICTION_FAST;
+ } else {
+ float t = mVelocity / VELOCITY_THRESHOLD;
+ mVelocity *= FloatUtils.interpolate(FRICTION_SLOW, FRICTION_FAST, t);
+ }
+ } else {
+ // Otherwise, decrease the velocity linearly.
+ float elasticity = 1.0f - excess / (getViewportLength() * SNAP_LIMIT);
+ if (getOverscroll() == Overscroll.MINUS) {
+ mVelocity = Math.min((mVelocity + OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
+ } else { // must be Overscroll.PLUS
+ mVelocity = Math.max((mVelocity - OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
+ }
+ }
+
+ return true;
+ }
+
+ void stopFling() {
+ mVelocity = 0.0f;
+ mFlingState = FlingStates.STOPPED;
+ }
+
+ // Performs displacement of the viewport position according to the current velocity.
+ void displace() {
+ if (!mSubscroller.scrolling() && !scrollable())
+ return;
+
+ if (mFlingState == FlingStates.PANNING)
+ mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance();
+ else
+ mDisplacement += mVelocity;
+ }
+
+ float resetDisplacement() {
+ float d = mDisplacement;
+ mDisplacement = 0.0f;
+ return d;
+ }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java
new file mode 100644
index 000000000000..c3cacccf805e
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java
@@ -0,0 +1,921 @@
+/* -*- 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-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Patrick Walton <pcwalton@mozilla.com>
+ * Kartikaya Gupta <kgupta@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.json.JSONException;
+import org.libreoffice.LOKitShell;
+import org.libreoffice.LibreOfficeMainActivity;
+import org.mozilla.gecko.gfx.FloatSize;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.PointUtils;
+import org.mozilla.gecko.gfx.ViewportMetrics;
+import org.mozilla.gecko.util.FloatUtils;
+import org.mozilla.gecko.GeckoEventListener;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+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 SimpleScaleGestureDetector.SimpleScaleGestureListener, GeckoEventListener
+{
+ private static final String LOGTAG = "GeckoPanZoomController";
+
+ private static String MESSAGE_ZOOM_RECT = "Browser:ZoomToRect";
+ private static String MESSAGE_ZOOM_PAGE = "Browser:ZoomToPageWidth";
+
+ // Animation stops if the velocity is below this value when overscrolled or panning.
+ private static final float STOPPED_THRESHOLD = 4.0f;
+
+ // Animation stops is the velocity is below this threshold when flinging.
+ private static final float FLING_STOPPED_THRESHOLD = 0.1f;
+
+ // The distance the user has to pan before we recognize it as such (e.g. to avoid 1-pixel pans
+ // between the touch-down and touch-up of a click). In units of density-independent pixels.
+ public static final float PAN_THRESHOLD = 1/16f * LOKitShell.getDpi();
+
+ // Angle from axis within which we stay axis-locked
+ private static final double AXIS_LOCK_ANGLE = Math.PI / 6.0; // 30 degrees
+
+ // The maximum amount we allow you to zoom into a page
+ private static final float MAX_ZOOM = 4.0f;
+
+ /* 16 precomputed frames of the _ease-out_ animation from the CSS Transitions specification. */
+ private static final float[] EASE_OUT_ANIMATION_FRAMES = {
+ 0.00000f, /* 0 */
+ 0.10211f, /* 1 */
+ 0.19864f, /* 2 */
+ 0.29043f, /* 3 */
+ 0.37816f, /* 4 */
+ 0.46155f, /* 5 */
+ 0.54054f, /* 6 */
+ 0.61496f, /* 7 */
+ 0.68467f, /* 8 */
+ 0.74910f, /* 9 */
+ 0.80794f, /* 10 */
+ 0.86069f, /* 11 */
+ 0.90651f, /* 12 */
+ 0.94471f, /* 13 */
+ 0.97401f, /* 14 */
+ 0.99309f, /* 15 */
+ };
+
+ 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_LOCKED, /* touch-start followed by move (i.e. panning with axis lock) */
+ PANNING, /* panning without axis lock */
+ PANNING_HOLD, /* in panning, but not moving.
+ * similar to TOUCHING but after starting a pan */
+ PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */
+ PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
+ ANIMATED_ZOOM /* animated zoom to a new rect */
+ }
+
+ private final LayerController mController;
+ private final SubdocumentScrollHelper mSubscroller;
+ private final Axis mX;
+ private final Axis mY;
+
+ private Thread mMainThread;
+
+ /* The timer that handles flings or bounces. */
+ private Timer mAnimationTimer;
+ /* The runnable being scheduled by the animation timer. */
+ private AnimationRunnable mAnimationRunnable;
+ /* The zoom focus at the first zoom event (in page coordinates). */
+ private PointF mLastZoomFocus;
+ /* The time the last motion event took place. */
+ private long mLastEventTime;
+ /* Current state the pan/zoom UI is in. */
+ private PanZoomState mState;
+
+ public PanZoomController(LayerController controller) {
+ mController = controller;
+ mSubscroller = new SubdocumentScrollHelper(this);
+ mX = new AxisX(mSubscroller);
+ mY = new AxisY(mSubscroller);
+
+ mMainThread = LibreOfficeMainActivity.mAppContext.getMainLooper().getThread();
+ checkMainThread();
+
+ mState = PanZoomState.NOTHING;
+
+ //GeckoAppShell.registerGeckoEventListener(MESSAGE_ZOOM_RECT, this);
+ //GeckoAppShell.registerGeckoEventListener(MESSAGE_ZOOM_PAGE, this);
+ }
+
+ // for debugging bug 713011; it can be taken out once that is resolved.
+ private void checkMainThread() {
+ if (mMainThread != Thread.currentThread()) {
+ // log with full stack trace
+ Log.e(LOGTAG, "Uh-oh, we're running on the wrong thread!", new Exception());
+ }
+ }
+
+ public void handleMessage(String event, JSONObject message) {
+ Log.i(LOGTAG, "Got message: " + event);
+ try {
+ if (MESSAGE_ZOOM_RECT.equals(event)) {
+ float x = (float)message.getDouble("x");
+ float y = (float)message.getDouble("y");
+ final RectF zoomRect = new RectF(x, y,
+ x + (float)message.getDouble("w"),
+ y + (float)message.getDouble("h"));
+ mController.post(new Runnable() {
+ public void run() {
+ animatedZoomTo(zoomRect);
+ }
+ });
+ } else if (MESSAGE_ZOOM_PAGE.equals(event)) {
+ FloatSize pageSize = mController.getPageSize();
+
+ RectF viewableRect = mController.getViewport();
+ float y = viewableRect.top;
+ // attempt to keep zoom keep focused on the center of the viewport
+ float newHeight = viewableRect.height() * pageSize.width / viewableRect.width();
+ float dh = viewableRect.height() - newHeight; // increase in the height
+ final RectF r = new RectF(0.0f,
+ y + dh/2,
+ pageSize.width,
+ y + dh/2 + newHeight);
+ mController.post(new Runnable() {
+ public void run() {
+ animatedZoomTo(r);
+ }
+ });
+ }
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
+ }
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction() & MotionEvent.ACTION_MASK) {
+ 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;
+ }
+ }
+
+ /** This function must be called from the UI thread. */
+ public void abortAnimation() {
+ checkMainThread();
+ // this happens when gecko changes the viewport on us or if the device is rotated.
+ // if that's the case, abort any animation in progress and re-zoom so that the page
+ // snaps to edges. for other cases (where the user's finger(s) are down) don't do
+ // anything special.
+ switch (mState) {
+ case FLING:
+ mX.stopFling();
+ mY.stopFling();
+ mState = PanZoomState.NOTHING;
+ // fall through
+ case ANIMATED_ZOOM:
+ // the zoom that's in progress likely makes no sense any more (such as if
+ // the screen orientation changed) so abort it
+ // fall through
+ case NOTHING:
+ // Don't do animations here; they're distracting and can cause flashes on page
+ // transitions.
+ mController.setViewportMetrics(getValidViewportMetrics());
+ mController.notifyLayerClientOfGeometryChange();
+ break;
+ }
+ }
+
+ /** This must be called on the UI thread. */
+ public void pageSizeUpdated() {
+ if (mState == PanZoomState.NOTHING) {
+ ViewportMetrics validated = getValidViewportMetrics();
+ if (! mController.getViewportMetrics().fuzzyEquals(validated)) {
+ // page size changed such that we are now in overscroll. snap to the
+ // the nearest valid viewport
+ mController.setViewportMetrics(validated);
+ mController.notifyLayerClientOfGeometryChange();
+ }
+ }
+ }
+
+ /*
+ * Panning/scrolling
+ */
+
+ private boolean onTouchStart(MotionEvent event) {
+ Log.d(LOGTAG, "onTouchStart in state " + mState);
+ // user is taking control of movement, so stop
+ // any auto-movement we have going
+ stopAnimationTimer();
+ mSubscroller.cancel();
+
+ switch (mState) {
+ case ANIMATED_ZOOM:
+ return false;
+ case FLING:
+ case NOTHING:
+ startTouch(event.getX(0), event.getY(0), event.getEventTime());
+ return false;
+ case TOUCHING:
+ case PANNING:
+ case PANNING_LOCKED:
+ case PANNING_HOLD:
+ case PANNING_HOLD_LOCKED:
+ case PINCHING:
+ Log.e(LOGTAG, "Received impossible touch down while in " + mState);
+ return false;
+ }
+ Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchStart");
+ return false;
+ }
+
+ private boolean onTouchMove(MotionEvent event) {
+ Log.d(LOGTAG, "onTouchMove in state " + mState);
+
+ switch (mState) {
+ case NOTHING:
+ case FLING:
+ // should never happen
+ Log.e(LOGTAG, "Received impossible touch move while in " + mState);
+ return false;
+
+ case TOUCHING:
+ if (panDistance(event) < PAN_THRESHOLD) {
+ return false;
+ }
+ cancelTouch();
+ startPanning(event.getX(0), event.getY(0), event.getEventTime());
+ //GeckoApp.mAppContext.hidePlugins(false /* don't hide layers */);
+ //GeckoApp.mAutoCompletePopup.hide();
+ track(event);
+ return true;
+
+ case PANNING_HOLD_LOCKED:
+ //GeckoApp.mAutoCompletePopup.hide();
+ mState = PanZoomState.PANNING_LOCKED;
+ // fall through
+ case PANNING_LOCKED:
+ track(event);
+ return true;
+
+ case PANNING_HOLD:
+ //GeckoApp.mAutoCompletePopup.hide();
+ mState = PanZoomState.PANNING;
+ // fall through
+ case PANNING:
+ track(event);
+ return true;
+
+ case ANIMATED_ZOOM:
+ case PINCHING:
+ // scale gesture listener will handle this
+ return false;
+ }
+ Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchMove");
+ return false;
+ }
+
+ private boolean onTouchEnd(MotionEvent event) {
+ Log.d(LOGTAG, "onTouchEnd in " + mState);
+
+ switch (mState) {
+ case NOTHING:
+ case FLING:
+ // should never happen
+ Log.e(LOGTAG, "Received impossible touch end while in " + mState);
+ return false;
+ case TOUCHING:
+ mState = PanZoomState.NOTHING;
+ // the switch into TOUCHING might have happened while the page was
+ // snapping back after overscroll. we need to finish the snap if that
+ // was the case
+ bounce();
+ return false;
+ case PANNING:
+ case PANNING_LOCKED:
+ case PANNING_HOLD:
+ case PANNING_HOLD_LOCKED:
+ mState = PanZoomState.FLING;
+ fling();
+ return true;
+ case PINCHING:
+ mState = PanZoomState.NOTHING;
+ return true;
+ case ANIMATED_ZOOM:
+ return false;
+ }
+ Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchEnd");
+ return false;
+ }
+
+ private boolean onTouchCancel(MotionEvent event) {
+ Log.d(LOGTAG, "onTouchCancel in " + mState);
+
+ mState = PanZoomState.NOTHING;
+ // ensure we snap back if we're overscrolled
+ bounce();
+ return false;
+ }
+
+ private void startTouch(float x, float y, long time) {
+ mX.startTouch(x);
+ mY.startTouch(y);
+ mState = PanZoomState.TOUCHING;
+ mLastEventTime = time;
+ }
+
+ private void startPanning(float x, float y, long time) {
+ float dx = mX.panDistance(x);
+ float dy = mY.panDistance(y);
+ double angle = Math.atan2(dy, dx); // range [-pi, pi]
+ angle = Math.abs(angle); // range [0, pi]
+
+ // When the touch move breaks through the pan threshold, reposition the touch down origin
+ // so the page won't jump when we start panning.
+ mX.startTouch(x);
+ mY.startTouch(y);
+ mLastEventTime = time;
+
+ if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) {
+ mY.setScrollingDisabled(true);
+ mState = PanZoomState.PANNING_LOCKED;
+ } else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) {
+ mX.setScrollingDisabled(true);
+ mState = PanZoomState.PANNING_LOCKED;
+ } else {
+ mState = PanZoomState.PANNING;
+ }
+ }
+
+ private float panDistance(MotionEvent move) {
+ float dx = mX.panDistance(move.getX(0));
+ float dy = mY.panDistance(move.getY(0));
+ return FloatMath.sqrt(dx * dx + dy * dy);
+ }
+
+ private void track(float x, float y, long time) {
+ float timeDelta = (float)(time - mLastEventTime);
+ if (FloatUtils.fuzzyEquals(timeDelta, 0)) {
+ // probably a duplicate event, ignore it. using a zero timeDelta will mess
+ // up our velocity
+ return;
+ }
+ mLastEventTime = time;
+
+ mX.updateWithTouchAt(x, timeDelta);
+ mY.updateWithTouchAt(y, timeDelta);
+ }
+
+ private void track(MotionEvent event) {
+ mX.saveTouchPos();
+ mY.saveTouchPos();
+
+ for (int i = 0; i < event.getHistorySize(); i++) {
+ track(event.getHistoricalX(0, i),
+ event.getHistoricalY(0, i),
+ event.getHistoricalEventTime(i));
+ }
+ track(event.getX(0), event.getY(0), event.getEventTime());
+
+ if (stopped()) {
+ if (mState == PanZoomState.PANNING) {
+ mState = PanZoomState.PANNING_HOLD;
+ } else if (mState == PanZoomState.PANNING_LOCKED) {
+ mState = PanZoomState.PANNING_HOLD_LOCKED;
+ } else {
+ // should never happen, but handle anyway for robustness
+ Log.e(LOGTAG, "Impossible case " + mState + " when stopped in track");
+ mState = PanZoomState.PANNING_HOLD_LOCKED;
+ }
+ }
+
+ mX.startPan();
+ mY.startPan();
+ updatePosition();
+ }
+
+ private void fling() {
+ updatePosition();
+
+ stopAnimationTimer();
+
+ boolean stopped = stopped();
+ mX.startFling(stopped);
+ mY.startFling(stopped);
+
+ startAnimationTimer(new FlingRunnable());
+ }
+
+ /* Performs a bounce-back animation to the given viewport metrics. */
+ private void bounce(ViewportMetrics metrics) {
+ stopAnimationTimer();
+
+ ViewportMetrics bounceStartMetrics = new ViewportMetrics(mController.getViewportMetrics());
+ if (bounceStartMetrics.fuzzyEquals(metrics)) {
+ mState = PanZoomState.NOTHING;
+ return;
+ }
+
+ mState = PanZoomState.FLING;
+ Log.d(LOGTAG, "end bounce at " + metrics);
+
+ startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics));
+ }
+
+ /* Performs a bounce-back animation to the nearest valid viewport metrics. */
+ private void bounce() {
+ bounce(getValidViewportMetrics());
+ }
+
+ /* Starts the fling or bounce animation. */
+ private void startAnimationTimer(final AnimationRunnable runnable) {
+ if (mAnimationTimer != null) {
+ Log.e(LOGTAG, "Attempted to start a new fling without canceling the old one!");
+ stopAnimationTimer();
+ }
+
+ //GeckoApp.mAppContext.hidePlugins(false /* don't hide layers */);
+
+ mAnimationTimer = new Timer("Animation Timer");
+ mAnimationRunnable = runnable;
+ mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() { mController.post(runnable); }
+ }, 0, 1000L/60L);
+ }
+
+ /* Stops the fling or bounce animation. */
+ private void stopAnimationTimer() {
+ if (mAnimationTimer != null) {
+ mAnimationTimer.cancel();
+ mAnimationTimer = null;
+ }
+ if (mAnimationRunnable != null) {
+ mAnimationRunnable.terminate();
+ mAnimationRunnable = null;
+ }
+
+ //GeckoApp.mAppContext.showPlugins();
+ }
+
+ private float getVelocity() {
+ float xvel = mX.getRealVelocity();
+ float yvel = mY.getRealVelocity();
+ return FloatMath.sqrt(xvel * xvel + yvel * yvel);
+ }
+
+ private boolean stopped() {
+ return getVelocity() < STOPPED_THRESHOLD;
+ }
+
+ PointF getDisplacement() {
+ return new PointF(mX.resetDisplacement(), mY.resetDisplacement());
+ }
+
+ private void updatePosition() {
+ mX.displace();
+ mY.displace();
+ PointF displacement = getDisplacement();
+ if (! mSubscroller.scrollBy(displacement)) {
+ synchronized (mController) {
+ mController.scrollBy(displacement);
+ }
+ }
+ }
+
+ private abstract class AnimationRunnable implements Runnable {
+ private boolean mAnimationTerminated;
+
+ /* This should always run on the UI thread */
+ public final void run() {
+ /*
+ * Since the animation timer queues this runnable on the UI thread, it
+ * is possible that even when the animation timer is cancelled, there
+ * are multiple instances of this queued, so we need to have another
+ * mechanism to abort. This is done by using the mAnimationTerminated flag.
+ */
+ if (mAnimationTerminated) {
+ return;
+ }
+ animateFrame();
+ }
+
+ protected abstract void animateFrame();
+
+ /* This should always run on the UI thread */
+ protected final void terminate() {
+ mAnimationTerminated = true;
+ }
+ }
+
+ /* The callback that performs the bounce animation. */
+ private class BounceRunnable extends AnimationRunnable {
+ /* The current frame of the bounce-back animation */
+ private int mBounceFrame;
+ /*
+ * The viewport metrics that represent the start and end of the bounce-back animation,
+ * respectively.
+ */
+ private ViewportMetrics mBounceStartMetrics;
+ private ViewportMetrics mBounceEndMetrics;
+
+ BounceRunnable(ViewportMetrics startMetrics, ViewportMetrics endMetrics) {
+ mBounceStartMetrics = startMetrics;
+ mBounceEndMetrics = endMetrics;
+ }
+
+ protected void animateFrame() {
+ /*
+ * The pan/zoom controller might have signaled to us that it wants to abort the
+ * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
+ * out.
+ */
+ if (mState != PanZoomState.FLING) {
+ finishAnimation();
+ return;
+ }
+
+ /* Perform the next frame of the bounce-back animation. */
+ if (mBounceFrame < EASE_OUT_ANIMATION_FRAMES.length) {
+ advanceBounce();
+ return;
+ }
+
+ /* Finally, if there's nothing else to do, complete the animation and go to sleep. */
+ finishBounce();
+ finishAnimation();
+ mState = PanZoomState.NOTHING;
+ }
+
+ /* Performs one frame of a bounce animation. */
+ private void advanceBounce() {
+ synchronized (mController) {
+ float t = EASE_OUT_ANIMATION_FRAMES[mBounceFrame];
+ ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
+ mController.setViewportMetrics(newMetrics);
+ mController.notifyLayerClientOfGeometryChange();
+ mBounceFrame++;
+ }
+ }
+
+ /* Concludes a bounce animation and snaps the viewport into place. */
+ private void finishBounce() {
+ synchronized (mController) {
+ mController.setViewportMetrics(mBounceEndMetrics);
+ mController.notifyLayerClientOfGeometryChange();
+ mBounceFrame = -1;
+ }
+ }
+ }
+
+ // The callback that performs the fling animation.
+ private class FlingRunnable extends AnimationRunnable {
+ protected void animateFrame() {
+ /*
+ * The pan/zoom controller might have signaled to us that it wants to abort the
+ * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
+ * out.
+ */
+ if (mState != PanZoomState.FLING) {
+ finishAnimation();
+ return;
+ }
+
+ /* Advance flings, if necessary. */
+ boolean flingingX = mX.advanceFling();
+ boolean flingingY = mY.advanceFling();
+
+ boolean overscrolled = (mX.overscrolled() || mY.overscrolled());
+
+ /* If we're still flinging in any direction, update the origin. */
+ if (flingingX || flingingY) {
+ updatePosition();
+
+ /*
+ * Check to see if we're still flinging with an appreciable velocity. The threshold is
+ * higher in the case of overscroll, so we bounce back eagerly when overscrolling but
+ * coast smoothly to a stop when not. In other words, require a greater velocity to
+ * maintain the fling once we enter overscroll.
+ */
+ float threshold = (overscrolled && !mSubscroller.scrolling() ? STOPPED_THRESHOLD : FLING_STOPPED_THRESHOLD);
+ if (getVelocity() >= threshold) {
+ // we're still flinging
+ return;
+ }
+
+ mX.stopFling();
+ mY.stopFling();
+ }
+
+ /* Perform a bounce-back animation if overscrolled. */
+ if (overscrolled) {
+ bounce();
+ } else {
+ finishAnimation();
+ mState = PanZoomState.NOTHING;
+ }
+ }
+ }
+
+ private void finishAnimation() {
+ checkMainThread();
+
+ Log.d(LOGTAG, "Finishing animation at " + mController.getViewportMetrics());
+ stopAnimationTimer();
+
+ // Force a viewport synchronisation
+ //GeckoApp.mAppContext.showPlugins();
+ mController.setForceRedraw();
+ mController.notifyLayerClientOfGeometryChange();
+ }
+
+ /* Returns the nearest viewport metrics with no overscroll visible. */
+ private ViewportMetrics getValidViewportMetrics() {
+ return getValidViewportMetrics(new ViewportMetrics(mController.getViewportMetrics()));
+ }
+
+ private ViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) {
+ Log.d(LOGTAG, "generating valid viewport using " + viewportMetrics);
+
+ /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */
+ float zoomFactor = viewportMetrics.getZoomFactor();
+ FloatSize pageSize = viewportMetrics.getPageSize();
+ RectF viewport = viewportMetrics.getViewport();
+
+ float focusX = viewport.width() / 2.0f;
+ float focusY = viewport.height() / 2.0f;
+ float minZoomFactor = 0.0f;
+ if (viewport.width() > pageSize.width && pageSize.width > 0) {
+ float scaleFactor = viewport.width() / pageSize.width;
+ minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
+ focusX = 0.0f;
+ }
+ if (viewport.height() > pageSize.height && pageSize.height > 0) {
+ float scaleFactor = viewport.height() / pageSize.height;
+ minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
+ focusY = 0.0f;
+ }
+
+ if (!FloatUtils.fuzzyEquals(minZoomFactor, 0.0f)) {
+ // if one (or both) of the page dimensions is smaller than the viewport,
+ // zoom using the top/left as the focus on that axis. this prevents the
+ // scenario where, if both dimensions are smaller than the viewport, but
+ // by different scale factors, we end up scrolled to the end on one axis
+ // after applying the scale
+ PointF center = new PointF(focusX, focusY);
+ viewportMetrics.scaleTo(minZoomFactor, center);
+ } else if (zoomFactor > MAX_ZOOM) {
+ PointF center = new PointF(viewport.width() / 2.0f, viewport.height() / 2.0f);
+ viewportMetrics.scaleTo(MAX_ZOOM, center);
+ }
+
+ /* Now we pan to the right origin. */
+ viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
+ Log.d(LOGTAG, "generated valid viewport as " + viewportMetrics);
+
+ return viewportMetrics;
+ }
+
+ private class AxisX extends Axis {
+ AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); }
+ @Override
+ public float getOrigin() { return mController.getOrigin().x; }
+ @Override
+ protected float getViewportLength() { return mController.getViewportSize().width; }
+ @Override
+ protected float getPageLength() { return mController.getPageSize().width; }
+ }
+
+ private class AxisY extends Axis {
+ AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); }
+ @Override
+ public float getOrigin() { return mController.getOrigin().y; }
+ @Override
+ protected float getViewportLength() { return mController.getViewportSize().height; }
+ @Override
+ protected float getPageLength() { return mController.getPageSize().height; }
+ }
+
+ /*
+ * Zooming
+ */
+ @Override
+ public boolean onScaleBegin(SimpleScaleGestureDetector detector) {
+ Log.d(LOGTAG, "onScaleBegin in " + mState);
+
+ if (mState == PanZoomState.ANIMATED_ZOOM)
+ return false;
+
+ mState = PanZoomState.PINCHING;
+ mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY());
+ //GeckoApp.mAppContext.hidePlugins(false /* don't hide layers, only views */);
+ //GeckoApp.mAutoCompletePopup.hide();
+ cancelTouch();
+
+ return true;
+ }
+
+ @Override
+ public boolean onScale(SimpleScaleGestureDetector detector) {
+ Log.d(LOGTAG, "onScale in state " + mState);
+
+ if (mState == PanZoomState.ANIMATED_ZOOM)
+ return false;
+
+ float prevSpan = detector.getPreviousSpan();
+ if (FloatUtils.fuzzyEquals(prevSpan, 0.0f)) {
+ // let's eat this one to avoid setting the new zoom to infinity (bug 711453)
+ return true;
+ }
+
+ float spanRatio = detector.getCurrentSpan() / prevSpan;
+
+ /*
+ * Apply edge resistance if we're zoomed out smaller than the page size by scaling the zoom
+ * factor toward 1.0.
+ */
+ float resistance = Math.min(mX.getEdgeResistance(), mY.getEdgeResistance());
+ if (spanRatio > 1.0f)
+ spanRatio = 1.0f + (spanRatio - 1.0f) * resistance;
+ else
+ spanRatio = 1.0f - (1.0f - spanRatio) * resistance;
+
+ synchronized (mController) {
+ float newZoomFactor = mController.getZoomFactor() * spanRatio;
+ if (newZoomFactor >= MAX_ZOOM) {
+ // apply resistance when zooming past MAX_ZOOM,
+ // such that it asymptotically reaches MAX_ZOOM + 1.0
+ // but never exceeds that
+ float excessZoom = newZoomFactor - MAX_ZOOM;
+ excessZoom = 1.0f - (float)Math.exp(-excessZoom);
+ newZoomFactor = MAX_ZOOM + excessZoom;
+ }
+
+ mController.scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(),
+ mLastZoomFocus.y - detector.getFocusY()));
+ PointF focus = new PointF(detector.getFocusX(), detector.getFocusY());
+ mController.scaleWithFocus(newZoomFactor, focus);
+ }
+
+ mLastZoomFocus.set(detector.getFocusX(), detector.getFocusY());
+
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(SimpleScaleGestureDetector detector) {
+ Log.d(LOGTAG, "onScaleEnd in " + mState);
+
+ if (mState == PanZoomState.ANIMATED_ZOOM)
+ return;
+
+ // switch back to the touching state
+ startTouch(detector.getFocusX(), detector.getFocusY(), detector.getEventTime());
+
+ // Force a viewport synchronisation
+ //GeckoApp.mAppContext.showPlugins();
+ mController.setForceRedraw();
+ mController.notifyLayerClientOfGeometryChange();
+ }
+
+ public boolean getRedrawHint() {
+ return (mState != PanZoomState.PINCHING && mState != PanZoomState.ANIMATED_ZOOM);
+ }
+
+ private void sendPointToGecko(String event, MotionEvent motionEvent) {
+ String json;
+ try {
+ PointF point = new PointF(motionEvent.getX(), motionEvent.getY());
+ point = mController.convertViewPointToLayerPoint(point);
+ if (point == null) {
+ return;
+ }
+ json = PointUtils.toJSON(point).toString();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Unable to convert point to JSON for " + event, e);
+ return;
+ }
+
+ //GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(event, json));
+ }
+
+ @Override
+ public void onLongPress(MotionEvent motionEvent) {
+ sendPointToGecko("Gesture:LongPress", motionEvent);
+ }
+
+ @Override
+ public boolean onDown(MotionEvent motionEvent) {
+ sendPointToGecko("Gesture:ShowPress", motionEvent);
+ return false;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
+ //GeckoApp.mAutoCompletePopup.hide();
+ sendPointToGecko("Gesture:SingleTap", motionEvent);
+ return true;
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent motionEvent) {
+ sendPointToGecko("Gesture:DoubleTap", motionEvent);
+ return true;
+ }
+
+ public void cancelTouch() {
+ //GeckoEvent e = GeckoEvent.createBroadcastEvent("Gesture:CancelTouch", "");
+ //GeckoAppShell.sendEventToGecko(e);
+ }
+
+ private boolean animatedZoomTo(RectF zoomToRect) {
+ //GeckoApp.mAutoCompletePopup.hide();
+
+ mState = PanZoomState.ANIMATED_ZOOM;
+ final float startZoom = mController.getZoomFactor();
+
+ RectF viewport = mController.getViewport();
+ // 1. adjust the aspect ratio of zoomToRect to match that of the current viewport,
+ // enlarging as necessary (if it gets too big, it will get shrunk in the next step).
+ // while enlarging make sure we enlarge equally on both sides to keep the target rect
+ // centered.
+ float targetRatio = viewport.width() / viewport.height();
+ float rectRatio = zoomToRect.width() / zoomToRect.height();
+ if (FloatUtils.fuzzyEquals(targetRatio, rectRatio)) {
+ // all good, do nothing
+ } else if (targetRatio < rectRatio) {
+ // need to increase zoomToRect height
+ float newHeight = zoomToRect.width() / targetRatio;
+ zoomToRect.top -= (newHeight - zoomToRect.height()) / 2;
+ zoomToRect.bottom = zoomToRect.top + newHeight;
+ } else { // targetRatio > rectRatio) {
+ // need to increase zoomToRect width
+ float newWidth = targetRatio * zoomToRect.height();
+ zoomToRect.left -= (newWidth - zoomToRect.width()) / 2;
+ zoomToRect.right = zoomToRect.left + newWidth;
+ }
+
+ float finalZoom = viewport.width() * startZoom / zoomToRect.width();
+
+ ViewportMetrics finalMetrics = new ViewportMetrics(mController.getViewportMetrics());
+ finalMetrics.setOrigin(new PointF(zoomToRect.left, zoomToRect.top));
+ finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f));
+
+ // 2. now run getValidViewportMetrics on it, so that the target viewport is
+ // clamped down to prevent overscroll, over-zoom, and other bad conditions.
+ finalMetrics = getValidViewportMetrics(finalMetrics);
+
+ bounce(finalMetrics);
+ return true;
+ }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SimpleScaleGestureDetector.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SimpleScaleGestureDetector.java
new file mode 100644
index 000000000000..4f9e39857e81
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SimpleScaleGestureDetector.java
@@ -0,0 +1,332 @@
+/* -*- 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) 2012
+ * 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.PointUtils;
+import org.json.JSONException;
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+import java.util.LinkedList;
+import java.util.ListIterator;
+import java.util.Stack;
+
+/**
+ * A less buggy, and smoother, replacement for the built-in Android ScaleGestureDetector.
+ *
+ * This gesture detector is more reliable than the built-in ScaleGestureDetector because:
+ *
+ * - It doesn't assume that pointer IDs are numbered 0 and 1.
+ *
+ * - It doesn't attempt to correct for "slop" when resting one's hand on the device. On some
+ * devices (e.g. the Droid X) this can cause the ScaleGestureDetector to lose track of how many
+ * pointers are down, with disastrous results (bug 706684).
+ *
+ * - Cancelling a zoom into a pan is handled correctly.
+ *
+ * - Starting with three or more fingers down, releasing fingers so that only two are down, and
+ * then performing a scale gesture is handled correctly.
+ *
+ * - It doesn't take pressure into account, which results in smoother scaling.
+ */
+public class SimpleScaleGestureDetector {
+ private static final String LOGTAG = "GeckoSimpleScaleGestureDetector";
+
+ private SimpleScaleGestureListener mListener;
+ private long mLastEventTime;
+
+ /* Information about all pointers that are down. */
+ private LinkedList<PointerInfo> mPointerInfo;
+
+ /** Creates a new gesture detector with the given listener. */
+ public SimpleScaleGestureDetector(SimpleScaleGestureListener listener) {
+ mListener = listener;
+ mPointerInfo = new LinkedList<PointerInfo>();
+ }
+
+ /** Forward touch events to this function. */
+ public void onTouchEvent(MotionEvent event) {
+ switch (event.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ onTouchStart(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ onTouchMove(event);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ onTouchEnd(event);
+ break;
+ }
+ }
+
+ private int getPointersDown() {
+ return mPointerInfo.size();
+ }
+
+ private int getActionIndex(MotionEvent event) {
+ return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ private void onTouchStart(MotionEvent event) {
+ mLastEventTime = event.getEventTime();
+ mPointerInfo.addFirst(PointerInfo.create(event, getActionIndex(event)));
+ if (getPointersDown() == 2) {
+ sendScaleGesture(EventType.BEGIN);
+ }
+ }
+
+ private void onTouchMove(MotionEvent event) {
+ mLastEventTime = event.getEventTime();
+ for (int i = 0; i < event.getPointerCount(); i++) {
+ PointerInfo pointerInfo = pointerInfoForEventIndex(event, i);
+ if (pointerInfo != null) {
+ pointerInfo.populate(event, i);
+ }
+ }
+
+ if (getPointersDown() == 2) {
+ sendScaleGesture(EventType.CONTINUE);
+ }
+ }
+
+ private void onTouchEnd(MotionEvent event) {
+ mLastEventTime = event.getEventTime();
+
+ int id = event.getPointerId(getActionIndex(event));
+ ListIterator<PointerInfo> iterator = mPointerInfo.listIterator();
+ while (iterator.hasNext()) {
+ PointerInfo pointerInfo = iterator.next();
+ if (pointerInfo.getId() != id) {
+ continue;
+ }
+
+ // One of the pointers we were tracking was lifted. Remove its info object from the
+ // list, recycle it to avoid GC pauses, and send an onScaleEnd() notification if this
+ // ended the gesture.
+ iterator.remove();
+ pointerInfo.recycle();
+ if (getPointersDown() == 1) {
+ sendScaleGesture(EventType.END);
+ }
+ }
+ }
+
+ /**
+ * Returns the X coordinate of the focus location (the midpoint of the two fingers). If only
+ * one finger is down, returns the location of that finger.
+ */
+ public float getFocusX() {
+ switch (getPointersDown()) {
+ case 1:
+ return mPointerInfo.getFirst().getCurrent().x;
+ case 2:
+ PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
+ return (pointerA.getCurrent().x + pointerB.getCurrent().x) / 2.0f;
+ }
+
+ Log.e(LOGTAG, "No gesture taking place in getFocusX()!");
+ return 0.0f;
+ }
+
+ /**
+ * Returns the Y coordinate of the focus location (the midpoint of the two fingers). If only
+ * one finger is down, returns the location of that finger.
+ */
+ public float getFocusY() {
+ switch (getPointersDown()) {
+ case 1:
+ return mPointerInfo.getFirst().getCurrent().y;
+ case 2:
+ PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
+ return (pointerA.getCurrent().y + pointerB.getCurrent().y) / 2.0f;
+ }
+
+ Log.e(LOGTAG, "No gesture taking place in getFocusY()!");
+ return 0.0f;
+ }
+
+ /** Returns the most recent distance between the two pointers. */
+ public float getCurrentSpan() {
+ if (getPointersDown() != 2) {
+ Log.e(LOGTAG, "No gesture taking place in getCurrentSpan()!");
+ return 0.0f;
+ }
+
+ PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
+ return PointUtils.distance(pointerA.getCurrent(), pointerB.getCurrent());
+ }
+
+ /** Returns the second most recent distance between the two pointers. */
+ public float getPreviousSpan() {
+ if (getPointersDown() != 2) {
+ Log.e(LOGTAG, "No gesture taking place in getPreviousSpan()!");
+ return 0.0f;
+ }
+
+ PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
+ PointF a = pointerA.getPrevious(), b = pointerB.getPrevious();
+ if (a == null || b == null) {
+ a = pointerA.getCurrent();
+ b = pointerB.getCurrent();
+ }
+
+ return PointUtils.distance(a, b);
+ }
+
+ /** Returns the time of the last event related to the gesture. */
+ public long getEventTime() {
+ return mLastEventTime;
+ }
+
+ /** Returns true if the scale gesture is in progress and false otherwise. */
+ public boolean isInProgress() {
+ return getPointersDown() == 2;
+ }
+
+ /* Sends the requested scale gesture notification to the listener. */
+ private void sendScaleGesture(EventType eventType) {
+ switch (eventType) {
+ case BEGIN: mListener.onScaleBegin(this); break;
+ case CONTINUE: mListener.onScale(this); break;
+ case END: mListener.onScaleEnd(this); break;
+ }
+ }
+
+ /*
+ * Returns the pointer info corresponding to the given pointer index, or null if the pointer
+ * isn't one that's being tracked.
+ */
+ private PointerInfo pointerInfoForEventIndex(MotionEvent event, int index) {
+ int id = event.getPointerId(index);
+ for (PointerInfo pointerInfo : mPointerInfo) {
+ if (pointerInfo.getId() == id) {
+ return pointerInfo;
+ }
+ }
+ return null;
+ }
+
+ private enum EventType {
+ BEGIN,
+ CONTINUE,
+ END,
+ }
+
+ /* Encapsulates information about one of the two fingers involved in the gesture. */
+ private static class PointerInfo {
+ /* A free list that recycles pointer info objects, to reduce GC pauses. */
+ private static Stack<PointerInfo> sPointerInfoFreeList;
+
+ private int mId;
+ private PointF mCurrent, mPrevious;
+
+ private PointerInfo() {
+ // External users should use create() instead.
+ }
+
+ /* Creates or recycles a new PointerInfo instance from an event and a pointer index. */
+ public static PointerInfo create(MotionEvent event, int index) {
+ if (sPointerInfoFreeList == null) {
+ sPointerInfoFreeList = new Stack<PointerInfo>();
+ }
+
+ PointerInfo pointerInfo;
+ if (sPointerInfoFreeList.empty()) {
+ pointerInfo = new PointerInfo();
+ } else {
+ pointerInfo = sPointerInfoFreeList.pop();
+ }
+
+ pointerInfo.populate(event, index);
+ return pointerInfo;
+ }
+
+ /*
+ * Fills in the fields of this instance from the given motion event and pointer index
+ * within that event.
+ */
+ public void populate(MotionEvent event, int index) {
+ mId = event.getPointerId(index);
+ mPrevious = mCurrent;
+ mCurrent = new PointF(event.getX(index), event.getY(index));
+ }
+
+ public void recycle() {
+ mId = -1;
+ mPrevious = mCurrent = null;
+ sPointerInfoFreeList.push(this);
+ }
+
+ public int getId() { return mId; }
+ public PointF getCurrent() { return mCurrent; }
+ public PointF getPrevious() { return mPrevious; }
+
+ @Override
+ public String toString() {
+ if (mId == -1) {
+ return "(up)";
+ }
+
+ try {
+ String prevString;
+ if (mPrevious == null) {
+ prevString = "n/a";
+ } else {
+ prevString = PointUtils.toJSON(mPrevious).toString();
+ }
+
+ // The current position should always be non-null.
+ String currentString = PointUtils.toJSON(mCurrent).toString();
+ return "id=" + mId + " cur=" + currentString + " prev=" + prevString;
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public static interface SimpleScaleGestureListener {
+ public boolean onScale(SimpleScaleGestureDetector detector);
+ public boolean onScaleBegin(SimpleScaleGestureDetector detector);
+ public void onScaleEnd(SimpleScaleGestureDetector detector);
+ }
+}
+
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java
new file mode 100644
index 000000000000..f24a5b7adaa1
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java
@@ -0,0 +1,140 @@
+/* -*- 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) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Kartikaya Gupta <kgupta@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 android.graphics.PointF;
+import android.os.Handler;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.GeckoEventListener;
+
+class SubdocumentScrollHelper implements GeckoEventListener {
+ private static final String LOGTAG = "GeckoSubdocumentScrollHelper";
+
+ private static String MESSAGE_PANNING_OVERRIDE = "Panning:Override";
+ private static String MESSAGE_CANCEL_OVERRIDE = "Panning:CancelOverride";
+ private static String MESSAGE_SCROLL = "Gesture:Scroll";
+ private static String MESSAGE_SCROLL_ACK = "Gesture:ScrollAck";
+
+ private final PanZoomController mPanZoomController;
+ private final Handler mUiHandler;
+
+ private boolean mOverridePanning;
+ private boolean mOverrideScrollAck;
+ private boolean mOverrideScrollPending;
+ private boolean mScrollSucceeded;
+
+ SubdocumentScrollHelper(PanZoomController controller) {
+ mPanZoomController = controller;
+ // mUiHandler will be bound to the UI thread since that's where this constructor runs
+ mUiHandler = new Handler();
+
+ //GeckoAppShell.registerGeckoEventListener(MESSAGE_PANNING_OVERRIDE, this);
+ //GeckoAppShell.registerGeckoEventListener(MESSAGE_CANCEL_OVERRIDE, this);
+ //GeckoAppShell.registerGeckoEventListener(MESSAGE_SCROLL_ACK, this);
+ }
+
+ boolean scrollBy(PointF displacement) {
+ if (!mOverridePanning) {
+ return false;
+ }
+
+ if (!mOverrideScrollAck) {
+ mOverrideScrollPending = true;
+ return true;
+ }
+
+ mOverrideScrollAck = false;
+ mOverrideScrollPending = false;
+
+ JSONObject json = new JSONObject();
+ try {
+ json.put("x", displacement.x);
+ json.put("y", displacement.y);
+ } catch (JSONException e) {
+ Log.e(LOGTAG, "Error forming subwindow scroll message: ", e);
+ }
+ //GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(MESSAGE_SCROLL, json.toString()));
+
+ return true;
+ }
+
+ void cancel() {
+ mOverridePanning = false;
+ }
+
+ boolean scrolling() {
+ return mOverridePanning;
+ }
+
+ boolean lastScrollSucceeded() {
+ return mScrollSucceeded;
+ }
+
+ // GeckoEventListener implementation
+
+ public void handleMessage(final String event, final JSONObject message) {
+ // this comes in on the gecko thread; hand off the handling to the UI thread
+ mUiHandler.post(new Runnable() {
+ public void run() {
+ Log.i(LOGTAG, "Got message: " + event);
+ try {
+ if (MESSAGE_PANNING_OVERRIDE.equals(event)) {
+ mOverridePanning = true;
+ mOverrideScrollAck = true;
+ mOverrideScrollPending = false;
+ mScrollSucceeded = true;
+ } else if (MESSAGE_CANCEL_OVERRIDE.equals(event)) {
+ mOverridePanning = false;
+ } else if (MESSAGE_SCROLL_ACK.equals(event)) {
+ mOverrideScrollAck = true;
+ mScrollSucceeded = message.getBoolean("scrolled");
+ if (mOverridePanning && mOverrideScrollPending) {
+ scrollBy(mPanZoomController.getDisplacement());
+ }
+ }
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Exception handling message", e);
+ }
+ }
+ });
+ }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/util/FloatUtils.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/util/FloatUtils.java
new file mode 100644
index 000000000000..b4bb249c4d5f
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/util/FloatUtils.java
@@ -0,0 +1,43 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.util;
+
+import android.graphics.PointF;
+
+import java.lang.IllegalArgumentException;
+
+public final class FloatUtils {
+ private FloatUtils() {}
+
+ public static boolean fuzzyEquals(float a, float b) {
+ return (Math.abs(a - b) < 1e-6);
+ }
+
+ public static boolean fuzzyEquals(PointF a, PointF b) {
+ return fuzzyEquals(a.x, b.x) && fuzzyEquals(a.y, b.y);
+ }
+
+ /*
+ * Returns the value that represents a linear transition between `from` and `to` at time `t`,
+ * which is on the scale [0, 1). Thus with t = 0.0f, this returns `from`; with t = 1.0f, this
+ * returns `to`; with t = 0.5f, this returns the value halfway from `from` to `to`.
+ */
+ public static float interpolate(float from, float to, float t) {
+ return from + (to - from) * t;
+ }
+
+ /**
+ * Returns 'value', clamped so that it isn't any lower than 'low', and it
+ * isn't any higher than 'high'.
+ */
+ public static float clamp(float value, float low, float high) {
+ if (high < low) {
+ throw new IllegalArgumentException(
+ "clamp called with invalid parameters (" + high + " < " + low + ")" );
+ }
+ return Math.max(low, Math.min(high, value));
+ }
+}