[Libreoffice-commits] .: android/experimental

Iain Billett iainb at kemper.freedesktop.org
Fri Jun 22 09:34:10 PDT 2012


 android/experimental/LibreOffice4Android/AndroidManifest.xml                                        |   40 
 android/experimental/LibreOffice4Android/Makefile                                                   |  272 +++
 android/experimental/LibreOffice4Android/build.xml                                                  |  114 +
 android/experimental/LibreOffice4Android/fonts.conf                                                 |  154 ++
 android/experimental/LibreOffice4Android/jni/Android.mk                                             |    8 
 android/experimental/LibreOffice4Android/jni/Application.mk                                         |    3 
 android/experimental/LibreOffice4Android/project.properties                                         |   14 
 android/experimental/LibreOffice4Android/res/drawable-hdpi/action_search.png                        |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/base.png                                 |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/calc.png                                 |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/draw.png                                 |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/dummy_page.png                           |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/folder.png                               |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/ic_launcher.png                          |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/impress.png                              |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/light_sort_by_size.png                   |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_grid.png                   |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_list.png                   |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/lo_icon.png                              |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/main.png                                 |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/math.png                                 |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/startcenter.png                          |binary
 android/experimental/LibreOffice4Android/res/drawable-hdpi/writer.png                               |binary
 android/experimental/LibreOffice4Android/res/drawable-ldpi/dummy_page.png                           |binary
 android/experimental/LibreOffice4Android/res/drawable-ldpi/ic_launcher.png                          |binary
 android/experimental/LibreOffice4Android/res/drawable-ldpi/lo_icon.png                              |binary
 android/experimental/LibreOffice4Android/res/drawable-mdpi/ic_launcher.png                          |binary
 android/experimental/LibreOffice4Android/res/drawable-mdpi/lo_icon.png                              |binary
 android/experimental/LibreOffice4Android/res/layout/file_explorer_grid_item.xml                     |   29 
 android/experimental/LibreOffice4Android/res/layout/file_grid.xml                                   |   20 
 android/experimental/LibreOffice4Android/res/layout/file_list.xml                                   |   13 
 android/experimental/LibreOffice4Android/res/layout/file_list_item.xml                              |   41 
 android/experimental/LibreOffice4Android/res/layout/main.xml                                        |   15 
 android/experimental/LibreOffice4Android/res/menu/view_menu.xml                                     |   20 
 android/experimental/LibreOffice4Android/res/values/arrays.xml                                      |   56 
 android/experimental/LibreOffice4Android/res/values/strings.xml                                     |   16 
 android/experimental/LibreOffice4Android/res/xml/libreoffice_preferences.xml                        |   21 
 android/experimental/LibreOffice4Android/src/com/polites/android/Animation.java                     |   32 
 android/experimental/LibreOffice4Android/src/com/polites/android/Animator.java                      |   96 +
 android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimation.java                |   74 +
 android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimationListener.java        |   29 
 android/experimental/LibreOffice4Android/src/com/polites/android/FlingListener.java                 |   45 
 android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageView.java              |  712 ++++++++++
 android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewListener.java      |   30 
 android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewTouchListener.java |  540 +++++++
 android/experimental/LibreOffice4Android/src/com/polites/android/MathUtils.java                     |   76 +
 android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimation.java                 |  107 +
 android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimationListener.java         |   27 
 android/experimental/LibreOffice4Android/src/com/polites/android/VectorF.java                       |   63 
 android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimation.java                 |  167 ++
 android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimationListener.java         |   26 
 android/experimental/LibreOffice4Android/src/org/libreoffice/android/examples/DocumentLoader.java   |  596 ++++++++
 android/experimental/LibreOffice4Android/src/org/libreoffice/ui/FileUtilities.java                  |  159 ++
 android/experimental/LibreOffice4Android/src/org/libreoffice/ui/GridItemAdapter.java                |   95 +
 android/experimental/LibreOffice4Android/src/org/libreoffice/ui/LibreOfficeUIActivity.java          |  548 +++++++
 android/experimental/LibreOffice4Android/src/org/libreoffice/ui/ListItemAdapter.java                |  159 ++
 android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PageView.java                       |   63 
 android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PreferenceEditor.java               |   18 
 android/experimental/LibreOffice4Android/src/org/libreoffice/ui/WriterViewerActivity.java           |   37 
 59 files changed, 4535 insertions(+)

New commits:
commit d3949d453d06bbdd88d33dd0d6e660f67fad546a
Author: Iain Billett <iainbillett at gmail.com>
Date:   Fri Jun 22 17:32:08 2012 +0100

    An new project to combine DocumentLoader with the Android UI. (Not building - see manifest)

diff --git a/android/experimental/LibreOffice4Android/AndroidManifest.xml b/android/experimental/LibreOffice4Android/AndroidManifest.xml
new file mode 100644
index 0000000..f521536
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="org.libreoffice" 
+      android:versionCode="1"
+      android:versionName="1.0">
+    
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="11"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    
+    <application 
+		 android:debuggable="true"
+		 android:icon="@drawable/lo_icon"
+		 android:label="@string/app_name" 
+		 android:theme="@android:style/Theme.Holo.Light">
+		 
+        <!-- Original Document Loader activity - file Viewer -->         
+        <activity android:name=".android.examples.DocumentLoader"
+                  android:label="LO DocumentLoader"
+                  android:configChanges="orientation|keyboardHidden">
+            <!-- <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>-->
+        </activity>
+        <!-- File Explorer Activities taken from eclipse workspace -->        
+        <activity
+            android:name=".ui.LibreOfficeUIActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:theme="@android:style/Theme.Holo.Light" android:name=".ui.WriterViewerActivity">
+            <intent-filter android:label="writer_viewer"></intent-filter>
+        </activity>
+        <activity android:name=".ui.PreferenceEditor"></activity>
+        
+    </application>
+</manifest>
diff --git a/android/experimental/LibreOffice4Android/Makefile b/android/experimental/LibreOffice4Android/Makefile
new file mode 100644
index 0000000..9423336
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/Makefile
@@ -0,0 +1,272 @@
+include ../../../config_host.mk
+
+# The package of this app
+APP_PACKAGE=org.libreoffice.android.examples
+
+# We can't keep assuming APP_DATA_PATH like this, surely this can vary with
+# Android versions and whatnot, this is temporary and works at least with the
+# SDK 16 emulator...
+
+# Probably would be best to just stop fooling around with the possibilities to
+# set various stuff with the -env command line parameters (and environment
+# variables?) and in a plethora of rc files, and hardcode construction of
+# *all* required pathnames based on the app installation location for Android
+# (and iOS), etc. We don't really win anything by having so many layers of
+# configurability on platforms like Android and iOS where apps based on LO
+# code are very much self-contained pre-packaged thingies.
+APP_DATA_PATH=/data/data/$(APP_PACKAGE)
+
+SODEST=libs/armeabi-v7a
+OBJLOCAL=obj/local/armeabi-v7a
+
+define COPYSO
+cp $(1) $(SODEST)$(if $(2),/$(2)) && $(STRIP) --strip-debug $(SODEST)$(if $(2),/$(2),/$(notdir $(1))) && \
+cp $(1) $(OBJLOCAL)$(if $(2),/$(2))
+endef
+
+define COPYJAR
+cp $(1) libs
+endef
+
+# The default target just builds.
+
+all: build-ant
+
+properties:
+	echo sdk.dir=$(ANDROID_SDK_HOME) >local.properties
+	echo sdk.dir=$(ANDROID_SDK_HOME) >../../Bootstrap/local.properties 
+
+copy-stuff: 
+# First always clean 
+	rm -rf libs $(OBJLOCAL)
+	mkdir -p $(SODEST) $(OBJLOCAL)
+#
+# Copy shared libraries (including UNO components) we need to
+# libs/armeabi-v7a so that ant will include them in the .apk.
+#
+# Copy them to obj/local/armeabi-v7a, too, where gdb will look for
+# them.
+#
+	for F in $(strip \
+		   analysislo \
+		   basebmplo \
+		   basegfxlo \
+		   bootstrap.uno \
+		   comphelpgcc3 \
+		   ctllo \
+		   datelo \
+		   dbaxmllo \
+		   dbtoolslo \
+		   evtattlo \
+		   expwrap.uno \
+		   fastsax.uno \
+		   fileacc \
+		   forlo \
+		   foruilo \
+		   frmlo \
+		   fsstorage.uno \
+		   gcc3_uno \
+		   hwplo \
+		   i18nisolang1gcc3 \
+		   i18npool.uno \
+		   i18nutilgcc3 \
+		   icudatalo \
+		   icui18nlo \
+		   iculelo \
+		   icuuclo \
+		   introspection.uno \
+		   java_uno \
+		   juh \
+		   juhx \
+		   jvmaccessgcc3 \
+		   jvmfwk \
+		   libotouchlo \
+		   lo-bootstrap \
+		   localebe1.uno \
+		   localedata_en \
+		   localedata_others \
+		   lwpftlo \
+		   mergedlo \
+		   msfilterlo \
+		   mswordlo \
+		   ooxlo \
+		   reflection.uno \
+		   reg \
+		   saxlo \
+		   sclo \
+		   scdlo \
+		   scfiltlo \
+		   sddlo \
+		   smdlo \
+		   sotlo \
+		   stocservices.uno \
+		   store \
+		   svgfilterlo \
+		   svllo \
+		   swdlo \
+		   swlo \
+		   t602filterlo \
+		   textinstream.uno \
+		   tllo \
+		   ucbhelper4gcc3 \
+		   ucppkg1 \
+		   uno_cppu \
+		   uno_cppuhelpergcc3 \
+		   uno_sal \
+		   uno_salhelpergcc3 \
+		   uno_cppuhelpergcc3 \
+		   unordflo \
+		   unoxmllo \
+		   utllo \
+		   vbahelperlo \
+		   vbaswobj.uno \
+		   wpftdrawlo \
+		   wpftwriterlo \
+		   vcllo \
+		   xml2 \
+		   xmlfdlo \
+		   xmlreader \
+		   xmlsecurity \
+		   xslt \
+		   xstor \
+		  ); do \
+	    $(call COPYSO,$(OUTDIR)/lib/lib$${F}.so); \
+	done
+#
+# Then the shared GNU C++ library
+	$(call COPYSO,$(ANDROID_NDK_HOME)/sources/cxx-stl/gnu-libstdc++/libs/armeabi-v7a/libgnustl_shared.so)
+#
+# Then other "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/bin/ure assets/lib assets/program assets/xml/ure assets/ComponentTarget/i18npool/util
+	cp $(OUTDIR)/bin/udkapi.rdb assets/bin
+	cp $(OUTDIR)/bin/types.rdb assets/bin
+	cp $(OUTDIR)/bin/ure/types.rdb assets/bin/ure
+# For some reason the vnd.sun.star.expand:$LO_LIB_DIR doesn't seem to work, it expands to empty!?
+# So just hardcode the known APP_DATA_PATH for now...
+	for F in xml/services xml/ure/services; do \
+		sed -e 's!uri="vnd.sun.star.expand:$$LO_LIB_DIR/!uri="file://$(APP_DATA_PATH)/lib/!g' <$(OUTDIR)/$$F.rdb >assets/$$F.rdb; \
+	done
+	cp $(SRC_ROOT)/odk/examples/java/DocumentHandling/test/test1.odt \
+	   $(SRC_ROOT)/sc/qa/unit/data/xls/border.xls \
+	   $(SRC_ROOT)/sw/qa/core/data/odt/test.odt \
+	   $(SRC_ROOT)/sw/qa/core/data/doc/testVba.doc \
+		assets
+	cp $(WORKDIR)/ComponentTarget/i18npool/util/i18npool.component assets/ComponentTarget/i18npool/util
+#
+	mkdir -p assets/ure/share/misc assets/share/registry/res assets/share/config/soffice.cfg
+	cp -R $(OUTDIR)/xml/*.xcd assets/share/registry
+	mv assets/share/registry/fcfg_langpack_en-US.xcd assets/share/registry/res
+	cp -R $(OUTDIR)/xml/uiconfig/* assets/share/config/soffice.cfg
+	cp -R $(OUTDIR)/xml/registry/* assets/share/registry
+#
+# 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_PATH)/cache" >> assets/rc
+	echo "OSL_SOCKET_PATH=$(APP_DATA_PATH)/cache" >> assets/rc
+#
+# Set up fundamentalrc
+	echo '[Bootstrap]' > assets/program/fundamentalrc
+	echo "LO_LIB_DIR=file:$(APP_DATA_PATH)/lib/" >> assets/program/fundamentalrc
+	echo "URE_LIB_DIR=file://$(APP_DATA_PATH)/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 module:$${BRAND_BASE_DIR}/share/registry/modules res:$${BRAND_BASE_DIR}/share/registry' >> assets/program/fundamentalrc
+	echo 'URE_BIN_DIR=file:///assets/ure/bin/dir/not-here/can-we/exec-anyway' >> assets/program/fundamentalrc
+	echo 'URE_MORE_TYPES=file:///assets/bin/ure/types.rdb file:///assets/bin/types.rdb' >> assets/program/fundamentalrc
+	echo 'URE_MORE_SERVICES=file:///assets/xml/services.rdb <$$BRAND_BASE_DIR/program/services>*' >> assets/program/fundamentalrc
+#
+# Set up unorc
+	echo '[Bootstrap]' > assets/program/unorc
+	echo "URE_INTERNAL_LIB_DIR=file://$(APP_DATA_PATH)/lib/" >> assets/program/unorc
+	echo 'UNO_TYPES=file:///assets/bin/ure/types.rdb file:///assets/bin/types.rdb $${URE_MORE_TYPES}' >> assets/program/unorc
+	echo 'UNO_SERVICES=file:///assets/xml/ure/services.rdb $${URE_MORE_SERVICES}' >> assets/program/unorc
+#
+# Set up bootstraprc
+	echo '[Bootstrap]' > assets/program/bootstraprc
+	echo 'InstallMode=<installmode>' >> assets/program/bootstraprc
+	echo 'ProductKey=LibreOffice 3.6' >> assets/program/bootstraprc
+	echo "UserInstallation=file://$(APP_DATA_PATH)" >> 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 'ProductBuildid=3' >> assets/program/versionrc
+	echo 'ProductMajor=360' >> assets/program/versionrc
+	echo 'ProductMinor=1' >> assets/program/versionrc
+	echo 'ProductSource=OOO350' >> assets/program/versionrc
+	echo 'ReferenceOOoMajorMinor=3.6' >> assets/program/versionrc
+#
+# .res files
+	mkdir -p assets/program/resource
+	cp $(OUTDIR)/bin/*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
+	mkdir -p assets/unpack/user/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.
+	cp $(OUTDIR)/pck/Liberation*.ttf assets/unpack/user/fonts
+	cp $(OUTDIR)/pck/Gen*.ttf assets/unpack/user/fonts
+	cp $(OUTDIR)/pck/opens___.ttf assets/unpack/user/fonts
+#
+# Then gdbserver and gdb.setup so that we can debug with ndk-gdb.
+#
+	cp $(ANDROID_NDK_HOME)/toolchains/arm-linux-androideabi-4.4.3/prebuilt/gdbserver $(SODEST)
+	echo set solib-search-path ./obj/local/armeabi-v7a >$(SODEST)/gdb.setup
+
+build-ant: copy-stuff properties
+#
+# Copy jar files we need, and even construct one.
+#
+	for F in $(strip \
+		   java_uno \
+		   juh \
+		   jurt \
+		   ridl \
+		   unoil \
+		   unoloader \
+		  ); do \
+	    $(call COPYJAR,$(OUTDIR)/bin/$${F}.jar); \
+	done
+#
+	unset JAVA_HOME && $(ANT) debug
+
+install: build-ant
+	unset JAVA_HOME && $(ANT) debug install
+	@echo
+	@echo 'Run it with something like what "make run" does (see Makefile)'
+	@echo
+
+uninstall:
+	$(ANDROID_SDK_HOME)/platform-tools/adb uninstall $(APP_PACKAGE)
+
+run:
+# /data/local/tmp/sample-document.odt
+	adb shell am start -n org.libreoffice.android.examples/.DocumentLoader -e input /assets/test1.odt
+
+
+clean: properties
+	$(ANT) clean
+	rm -rf assets libs $(SODEST) $(OBJLOCAL)
diff --git a/android/experimental/LibreOffice4Android/build.xml b/android/experimental/LibreOffice4Android/build.xml
new file mode 100644
index 0000000..5d96019
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/build.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="LibreOfficeDocumentLoader" 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="${sdk.dir}/tools/ant/build.xml" />
+
+    <!-- Re-define the "-package-resources" target to not compress resources -->
+
+    <target name="-package-resources" depends="-crunch">
+        <!-- only package resources if *not* a library project -->
+        <do-only-if-not-library elseText="Library project: do not package resources..." >
+            <aapt executable="${aapt}"
+                    command="package"
+                    versioncode="${version.code}"
+                    versionname="${version.name}"
+                    debug="${build.is.packaging.debug}"
+                    manifest="AndroidManifest.xml"
+                    assets="${asset.absolute.dir}"
+                    androidjar="${android.jar}"
+                    apkfolder="${out.absolute.dir}"
+                    nocrunch="${build.packaging.nocrunch}"
+                    resourcefilename="${resource.package.file.name}"
+                    resourcefilter="${aapt.resource.filter}"
+                    projectLibrariesResName="project.libraries.res"
+                    projectLibrariesPackageName="project.libraries.package"
+                    previousBuildType="${build.last.target}"
+                    buildType="${build.target}">
+                <res path="${out.res.absolute.dir}" />
+                <res path="${resource.absolute.dir}" />
+                <nocompress /> <!-- forces no compression on any files in assets or res/raw -->
+                <!-- <nocompress extension="xml" /> forces no compression on specific file extensions in assets and res/raw -->
+            </aapt>
+        </do-only-if-not-library>
+    </target>
+
+</project>
diff --git a/android/experimental/LibreOffice4Android/fonts.conf b/android/experimental/LibreOffice4Android/fonts.conf
new file mode 100644
index 0000000..699e9d1
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/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.android.examples/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/LibreOffice4Android/jni/Android.mk b/android/experimental/LibreOffice4Android/jni/Android.mk
new file mode 100644
index 0000000..939a1ea
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/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/LibreOffice4Android/jni/Application.mk b/android/experimental/LibreOffice4Android/jni/Application.mk
new file mode 100644
index 0000000..f326d1a
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/jni/Application.mk
@@ -0,0 +1,3 @@
+# File needed by ndk-gdb
+APP_ABI := armeabi-v7a
+APP_PLATFORM := android-14
diff --git a/android/experimental/LibreOffice4Android/project.properties b/android/experimental/LibreOffice4Android/project.properties
new file mode 100644
index 0000000..06b2d88
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/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-14
+
+# Use the Bootstrap class
+android.library.reference.1=../../Bootstrap
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/action_search.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/action_search.png
new file mode 100644
index 0000000..e6b7045
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/action_search.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/base.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/base.png
new file mode 100644
index 0000000..729dbcd
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/base.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/calc.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/calc.png
new file mode 100644
index 0000000..a3f5fd4
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/calc.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/draw.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/draw.png
new file mode 100644
index 0000000..b3ee114
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/draw.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/dummy_page.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/dummy_page.png
new file mode 100644
index 0000000..c58d276
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/dummy_page.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/folder.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/folder.png
new file mode 100644
index 0000000..9c9b42c
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/folder.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/ic_launcher.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/ic_launcher.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/impress.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/impress.png
new file mode 100644
index 0000000..5909f05
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/impress.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_sort_by_size.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_sort_by_size.png
new file mode 100644
index 0000000..3b34aaf
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_sort_by_size.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_grid.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_grid.png
new file mode 100644
index 0000000..ae138ed
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_grid.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_list.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_list.png
new file mode 100644
index 0000000..c5f6c97
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_list.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/lo_icon.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/lo_icon.png
new file mode 100644
index 0000000..2ef8641
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/lo_icon.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/main.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/main.png
new file mode 100644
index 0000000..7e8e2a0
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/main.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/math.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/math.png
new file mode 100644
index 0000000..50b8dc8
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/math.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/startcenter.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/startcenter.png
new file mode 100644
index 0000000..7e8e2a0
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/startcenter.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/writer.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/writer.png
new file mode 100644
index 0000000..2f4abcb
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-hdpi/writer.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-ldpi/dummy_page.png b/android/experimental/LibreOffice4Android/res/drawable-ldpi/dummy_page.png
new file mode 100644
index 0000000..c58d276
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-ldpi/dummy_page.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-ldpi/ic_launcher.png b/android/experimental/LibreOffice4Android/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-ldpi/ic_launcher.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-ldpi/lo_icon.png b/android/experimental/LibreOffice4Android/res/drawable-ldpi/lo_icon.png
new file mode 100644
index 0000000..95b3113
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-ldpi/lo_icon.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-mdpi/ic_launcher.png b/android/experimental/LibreOffice4Android/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-mdpi/ic_launcher.png differ
diff --git a/android/experimental/LibreOffice4Android/res/drawable-mdpi/lo_icon.png b/android/experimental/LibreOffice4Android/res/drawable-mdpi/lo_icon.png
new file mode 100644
index 0000000..4f3f89b
Binary files /dev/null and b/android/experimental/LibreOffice4Android/res/drawable-mdpi/lo_icon.png differ
diff --git a/android/experimental/LibreOffice4Android/res/layout/file_explorer_grid_item.xml b/android/experimental/LibreOffice4Android/res/layout/file_explorer_grid_item.xml
new file mode 100644
index 0000000..ce42e57
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/layout/file_explorer_grid_item.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+    
+	<ImageView
+        android:id="@+id/grid_item_image"
+        android:layout_width="50dp"
+        android:layout_height="75dp"
+        android:paddingTop="15dp"
+        android:paddingBottom="10dp"
+        android:layout_gravity="center" >
+    </ImageView>
+ 
+    <TextView
+        android:id="@+id/grid_item_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@+id/label"
+        android:paddingLeft="10dp"
+        android:paddingRight="10dp"
+        android:layout_gravity="center"
+        android:textSize="15dp" 
+        android:textStyle="bold"
+        android:maxLines="2">
+    </TextView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/android/experimental/LibreOffice4Android/res/layout/file_grid.xml b/android/experimental/LibreOffice4Android/res/layout/file_grid.xml
new file mode 100644
index 0000000..1e241c0
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/layout/file_grid.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <GridView
+        android:id="@+id/file_explorer_grid_view"
+    	android:layout_width="fill_parent" 
+	    android:layout_height="fill_parent"
+	    android:columnWidth="120dp"
+	    android:numColumns="auto_fit"
+	    android:verticalSpacing="10dp"
+	    android:horizontalSpacing="10dp"
+	    android:stretchMode="columnWidth"
+	    android:gravity="center">
+    </GridView>
+	
+	
+</LinearLayout>
\ No newline at end of file
diff --git a/android/experimental/LibreOffice4Android/res/layout/file_list.xml b/android/experimental/LibreOffice4Android/res/layout/file_list.xml
new file mode 100644
index 0000000..6ef0255
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/layout/file_list.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+    
+    <ListView
+        android:id="@+id/file_explorer_list_view"
+    	android:layout_width="fill_parent" 
+	    android:layout_height="fill_parent"
+	 ></ListView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/android/experimental/LibreOffice4Android/res/layout/file_list_item.xml b/android/experimental/LibreOffice4Android/res/layout/file_list_item.xml
new file mode 100644
index 0000000..0bff445
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/layout/file_list_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="48dp"
+    android:orientation="horizontal" >
+    <ImageView 
+        android:id="@+id/file_list_item_icon"
+        android:layout_height="match_parent"
+        android:layout_width="32dp"
+        android:layout_margin="8dp"
+        android:layout_gravity="center"/>
+    <LinearLayout
+        android:layout_width="match_parent"
+    	android:layout_height="48dp"
+    	android:orientation="horizontal">
+	    <TextView 
+	        android:id="@+id/file_list_item_name"
+	        android:layout_height="48dp"
+	        android:layout_width="0dp"
+	        android:textSize="15dp"
+	        android:textStyle="bold"
+	        android:layout_weight="2"
+	        android:gravity="center"/>
+	    <TextView 
+	        android:id="@+id/file_list_item_size"
+	        android:layout_height="48dp"
+	        android:layout_width="0dp"
+	        android:textSize="15dp"
+	        android:textStyle="bold"
+	        android:layout_weight="1"
+	        android:gravity="center"/>
+	    <TextView 
+	        android:id="@+id/file_list_item_date"
+	        android:layout_height="48dp"
+	        android:layout_width="0dp"
+	        android:textSize="15dp"
+	        android:textStyle="bold"
+	        android:layout_weight="2"
+	        android:gravity="center"/>
+	</LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/android/experimental/LibreOffice4Android/res/layout/main.xml b/android/experimental/LibreOffice4Android/res/layout/main.xml
new file mode 100644
index 0000000..6b97fe1
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/layout/main.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="#aaa"
+    android:orientation="vertical"
+    >
+
+    <org.libreoffice.ui.PageView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        />
+    
+
+</LinearLayout>
\ No newline at end of file
diff --git a/android/experimental/LibreOffice4Android/res/menu/view_menu.xml b/android/experimental/LibreOffice4Android/res/menu/view_menu.xml
new file mode 100644
index 0000000..87270d3
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/menu/view_menu.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_search"
+          android:icon="@drawable/action_search"
+          android:showAsAction="always" />
+    <item android:id="@+id/menu_view_toggle"
+          android:title="@string/grid_view"/>
+    <item android:id="@+id/menu_sort_size"
+          android:title="@string/menu_sort_size"
+          android:onClick="sortFiles"/>
+    <item android:id="@+id/menu_sort_az"
+          android:title="@string/menu_sort_az"
+          android:onClick="sortFiles"/>
+    <item android:id="@+id/menu_sort_modified"
+          android:title="@string/menu_sort_modified"
+          android:onClick="sortFiles"/>
+	<item android:id="@+id/menu_preferences"
+	          android:title="@string/menu_preferences"
+	          android:onClick="editPreferences"/>
+</menu>
\ No newline at end of file
diff --git a/android/experimental/LibreOffice4Android/res/values/arrays.xml b/android/experimental/LibreOffice4Android/res/values/arrays.xml
new file mode 100644
index 0000000..67a157d
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/values/arrays.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <integer-array name="FilterTypeValues">
+        <item >-1</item>
+        <item >0</item>
+        <item >1</item>
+        <item >2</item>
+    </integer-array>
+    <string-array name="FilterTypeStringValues">
+        <item >-1</item>
+        <item >0</item>
+        <item >1</item>
+        <item >2</item>
+    </string-array>
+    <string-array name="SortModeStringValues">
+        <item >0</item>
+        <item >1</item>
+        <item >2</item>
+        <item >3</item>
+        <item >4</item>
+        <item >5</item>
+    </string-array>
+    <!-- View Mode names,values -->
+    <string-array name="ViewModeNames">
+        <item >Grid</item>
+        <item >List</item>
+    </string-array>
+    <string-array name="ViewModeStringValues">
+        <item >0</item>
+        <item >1</item>
+    </string-array>
+    
+    <!-- Preference Name Arrays -->
+    <string-array name="file_view_modes">
+        <item >EVERYTHING</item>
+        <item >DOCUMENTS</item>
+        <item >SPREADSHEETS</item>
+        <item >PRESENTATIONS</item>
+    </string-array>
+    <string-array name="FilterTypeNames">
+        <item >Everything</item>
+        <item >Documents</item>
+        <item >Spreadsheets</item>
+        <item >Presentations</item>
+    </string-array>
+    <string-array name="SortModeNames">
+        <item >A-Z</item>
+        <item >Z-A</item>
+        <item >Oldest First</item>
+        <item >Newest First</item>
+        <item >Largest First</item>
+        <item >Smallest First</item>
+    </string-array>
+    
+    
+</resources>
\ No newline at end of file
diff --git a/android/experimental/LibreOffice4Android/res/values/strings.xml b/android/experimental/LibreOffice4Android/res/values/strings.xml
new file mode 100644
index 0000000..8951dd4
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/values/strings.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">LibreOfficeUI</string>
+    <string name="menu_search">Search</string>
+    <string name="list_view">List</string>
+    <string name="grid_view">Grid</string>
+    <string name="menu_sort_size">Sort By Size</string>
+    <string name="menu_sort_az">Sort A-Z</string>
+    <string name="menu_sort_modified">Sort by Date</string>
+    <string name="menu_preferences">Preferences</string>
+    <!-- Pref keys as resources ; Not currently used -->
+    <string name="EXPLORER_VIEW_TYPE_KEY">EXPLORER_VIEW_TYPE</string>
+    <string name="CURRENT_DIRECTORY_KEY">CURRENT_DIRECTORY</string>
+    
+
+</resources>
\ No newline at end of file
diff --git a/android/experimental/LibreOffice4Android/res/xml/libreoffice_preferences.xml b/android/experimental/LibreOffice4Android/res/xml/libreoffice_preferences.xml
new file mode 100644
index 0000000..d19d9e6
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/xml/libreoffice_preferences.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
+    <ListPreference 
+        android:title="Default File Filter"
+        android:summary="Set which files to show by default"
+        android:entries="@array/FilterTypeNames" 
+        android:entryValues="@array/FilterTypeStringValues" 
+        android:key="FILTER_MODE"/>
+    <ListPreference 
+        android:summary="Select how to order files; A-Z, by size, etc." 
+        android:key="SORT_MODE" 
+        android:title="File Order" android:entries="@array/SortModeNames" android:entryValues="@array/SortModeStringValues"/>
+    <ListPreference 
+        android:entries="@array/ViewModeNames" 
+        android:entryValues="@array/ViewModeStringValues" 
+        android:title="Default File Explorer View" 
+        android:key="EXPLORER_VIEW_TYPE" 
+        android:summary="View files as a grid or in a list. #not functional, yet."/>
+    
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/Animation.java b/android/experimental/LibreOffice4Android/src/com/polites/android/Animation.java
new file mode 100644
index 0000000..9936208
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/Animation.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+/**
+ * @author Jason Polites
+ *
+ */
+public interface Animation {
+
+	/**
+	 * Transforms the view.
+	 * @param view
+	 * @param diffTime
+	 * @return true if this animation should remain active.  False otherwise.
+	 */
+	public boolean update(GestureImageView view, long time);
+
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/Animator.java b/android/experimental/LibreOffice4Android/src/com/polites/android/Animator.java
new file mode 100644
index 0000000..fb0728b
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/Animator.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public class Animator extends Thread {
+
+	private GestureImageView view;
+	private Animation animation;
+	private boolean running = false;
+	private boolean active = false;
+	private long lastTime = -1L;
+
+	public Animator(GestureImageView view, String threadName) {
+		super(threadName);
+		this.view = view;
+	}
+
+	@Override
+	public void run() {
+
+		running = true;
+
+		while(running) {
+
+			while(active && animation != null) {
+				long time = System.currentTimeMillis();
+				active = animation.update(view, time - lastTime);
+				view.redraw();
+				lastTime = time;
+
+				while(active) {
+					try {
+						if(view.waitForDraw(32)) { // 30Htz
+							break;
+						}
+					}
+					catch (InterruptedException ignore) {
+						active = false;
+					}
+				}
+			}
+
+			synchronized(this) {
+				if(running) {
+					try {
+						wait();
+					}
+					catch (InterruptedException ignore) {}
+				}
+			}
+		}
+	}
+
+	public synchronized void finish() {
+		running = false;
+		active = false;
+		notifyAll();
+	}
+
+	public void play(Animation transformer) {
+		if(active) {
+			cancel();
+		}
+		this.animation = transformer;
+
+		activate();
+	}
+
+	public synchronized void activate() {
+		lastTime = System.currentTimeMillis();
+		active = true;
+		notifyAll();
+	}
+
+	public void cancel() {
+		active = false;
+	}
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimation.java b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimation.java
new file mode 100644
index 0000000..3124b62
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimation.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+/**
+ * @author Jason Polites
+ *
+ */
+public class FlingAnimation implements Animation {
+
+	private float velocityX;
+	private float velocityY;
+
+	private float factor = 0.85f;
+
+	private float threshold = 10;
+
+	private FlingAnimationListener listener;
+
+	/* (non-Javadoc)
+	 * @see com.polites.android.Transformer#update(com.polites.android.GestureImageView, long)
+	 */
+	@Override
+	public boolean update(GestureImageView view, long time) {
+		float seconds = (float) time / 1000.0f;
+
+		float dx = velocityX * seconds;
+		float dy = velocityY * seconds;
+
+		velocityX *= factor;
+		velocityY *= factor;
+
+		boolean active = (Math.abs(velocityX) > threshold && Math.abs(velocityY) > threshold);
+
+		if(listener != null) {
+			listener.onMove(dx, dy);
+
+			if(!active) {
+				listener.onComplete();
+			}
+		}
+
+		return active;
+	}
+
+	public void setVelocityX(float velocityX) {
+		this.velocityX = velocityX;
+	}
+
+	public void setVelocityY(float velocityY) {
+		this.velocityY = velocityY;
+	}
+
+	public void setFactor(float factor) {
+		this.factor = factor;
+	}
+
+	public void setListener(FlingAnimationListener listener) {
+		this.listener = listener;
+	}
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimationListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimationListener.java
new file mode 100644
index 0000000..b9611d5
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimationListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public interface FlingAnimationListener {
+
+	public void onMove(float x, float y);
+
+	public void onComplete();
+
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/FlingListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingListener.java
new file mode 100644
index 0000000..ab3007a
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingListener.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public class FlingListener extends SimpleOnGestureListener {
+
+	private float velocityX;
+	private float velocityY;
+
+	@Override
+	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+		this.velocityX = velocityX;
+		this.velocityY = velocityY;
+		return true;
+	}
+
+	public float getVelocityX() {
+		return velocityX;
+	}
+
+	public float getVelocityY() {
+		return velocityY;
+	}
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageView.java b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageView.java
new file mode 100644
index 0000000..1cde6e4
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageView.java
@@ -0,0 +1,712 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import java.io.InputStream;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ImageView;
+
+public class GestureImageView extends ImageView  {
+
+	public static final String GLOBAL_NS = "http://schemas.android.com/apk/res/android";
+	public static final String LOCAL_NS = "http://schemas.polites.com/android";
+
+	private final Semaphore drawLock = new Semaphore(0);
+	private Animator animator;
+
+	private Drawable drawable;
+
+	private float x = 0, y = 0;
+
+	private boolean layout = false;
+
+	private float scaleAdjust = 1.0f;
+	private float startingScale = -1.0f;
+
+	private float scale = 1.0f;
+	private float maxScale = 5.0f;
+	private float minScale = 0.75f;
+	private float fitScaleHorizontal = 1.0f;
+	private float fitScaleVertical = 1.0f;
+	private float rotation = 0.0f;
+
+	private float centerX;
+	private float centerY;
+
+	private Float startX, startY;
+
+	private int hWidth;
+	private int hHeight;
+
+	private int resId = -1;
+	private boolean recycle = false;
+	private boolean strict = false;
+
+	private int displayHeight;
+	private int displayWidth;
+
+	private int alpha = 255;
+	private ColorFilter colorFilter;
+
+	private int deviceOrientation = -1;
+	private int imageOrientation;
+
+	private GestureImageViewListener gestureImageViewListener;
+	private GestureImageViewTouchListener gestureImageViewTouchListener;
+
+	private OnTouchListener customOnTouchListener;
+	private OnClickListener onClickListener;
+
+	public GestureImageView(Context context, AttributeSet attrs, int defStyle) {
+		this(context, attrs);
+	}
+
+	public GestureImageView(Context context, AttributeSet attrs) {
+		super(context, attrs);
+
+		String scaleType = attrs.getAttributeValue(GLOBAL_NS, "scaleType");
+
+		if(scaleType == null || scaleType.trim().length() == 0) {
+			setScaleType(ScaleType.CENTER_INSIDE);
+		}
+
+		String strStartX = attrs.getAttributeValue(LOCAL_NS, "start-x");
+		String strStartY = attrs.getAttributeValue(LOCAL_NS, "start-y");
+
+		if(strStartX != null && strStartX.trim().length() > 0) {
+			startX = Float.parseFloat(strStartX);
+		}
+
+		if(strStartY != null && strStartY.trim().length() > 0) {
+			startY = Float.parseFloat(strStartY);
+		}
+
+		setStartingScale(attrs.getAttributeFloatValue(LOCAL_NS, "start-scale", startingScale));
+		setMinScale(attrs.getAttributeFloatValue(LOCAL_NS, "min-scale", minScale));
+		setMaxScale(attrs.getAttributeFloatValue(LOCAL_NS, "max-scale", maxScale));
+		setStrict(attrs.getAttributeBooleanValue(LOCAL_NS, "strict", strict));
+		setRecycle(attrs.getAttributeBooleanValue(LOCAL_NS, "recycle", recycle));
+
+		initImage();
+	}
+
+	public GestureImageView(Context context) {
+		super(context);
+		setScaleType(ScaleType.CENTER_INSIDE);
+		initImage();
+	}
+
+	@Override
+	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+		if(drawable != null) {
+			int orientation = getResources().getConfiguration().orientation;
+			if(orientation == Configuration.ORIENTATION_LANDSCAPE) {
+				displayHeight = MeasureSpec.getSize(heightMeasureSpec);
+
+				if(getLayoutParams().width == LayoutParams.WRAP_CONTENT) {
+					float ratio = (float) getImageWidth() / (float) getImageHeight();
+					displayWidth = Math.round( (float) displayHeight * ratio) ;
+				}
+				else {
+					displayWidth = MeasureSpec.getSize(widthMeasureSpec);
+				}
+			}
+			else {
+				displayWidth = MeasureSpec.getSize(widthMeasureSpec);
+				if(getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
+					float ratio = (float) getImageHeight() / (float) getImageWidth();
+					displayHeight = Math.round( (float) displayWidth * ratio) ;
+				}
+				else {
+					displayHeight = MeasureSpec.getSize(heightMeasureSpec);
+				}
+			}
+		}
+		else {
+			displayHeight = MeasureSpec.getSize(heightMeasureSpec);
+			displayWidth = MeasureSpec.getSize(widthMeasureSpec);
+		}
+
+		setMeasuredDimension(displayWidth, displayHeight);
+	}
+
+	@Override
+	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+		super.onLayout(changed, left, top, right, bottom);
+		if(changed || !layout) {
+			setupCanvas(displayWidth, displayHeight, getResources().getConfiguration().orientation);
+		}
+	}
+
+	protected void setupCanvas(int measuredWidth, int measuredHeight, int orientation) {
+
+		if(deviceOrientation != orientation) {
+			layout = false;
+			deviceOrientation = orientation;
+		}
+
+		if(drawable != null && !layout) {
+			int imageWidth = getImageWidth();
+			int imageHeight = getImageHeight();
+
+			hWidth = Math.round(((float)imageWidth / 2.0f));
+			hHeight = Math.round(((float)imageHeight / 2.0f));
+
+			measuredWidth -= (getPaddingLeft() + getPaddingRight());
+			measuredHeight -= (getPaddingTop() + getPaddingBottom());
+
+			computeCropScale(imageWidth, imageHeight, measuredWidth, measuredHeight);
+
+			if(startingScale <= 0.0f) {
+				computeStartingScale(imageWidth, imageHeight, measuredWidth, measuredHeight);
+			}
+
+			scaleAdjust = startingScale;
+
+			this.centerX = (float) measuredWidth / 2.0f;
+			this.centerY = (float) measuredHeight / 2.0f;
+
+			if(startX == null) {
+				x = centerX;
+			}
+			else {
+				x = startX;
+			}
+
+			if(startY == null) {
+				y = centerY;
+			}
+			else {
+				y = startY;
+			}
+
+			gestureImageViewTouchListener = new GestureImageViewTouchListener(this, measuredWidth, measuredHeight);
+
+			if(isLandscape()) {
+				gestureImageViewTouchListener.setMinScale(minScale * fitScaleHorizontal);
+			}
+			else {
+				gestureImageViewTouchListener.setMinScale(minScale * fitScaleVertical);
+			}
+
+
+			gestureImageViewTouchListener.setMaxScale(maxScale * startingScale);
+
+			gestureImageViewTouchListener.setFitScaleHorizontal(fitScaleHorizontal);
+			gestureImageViewTouchListener.setFitScaleVertical(fitScaleVertical);
+			gestureImageViewTouchListener.setCanvasWidth(measuredWidth);
+			gestureImageViewTouchListener.setCanvasHeight(measuredHeight);
+			gestureImageViewTouchListener.setOnClickListener(onClickListener);
+
+			drawable.setBounds(-hWidth,-hHeight,hWidth,hHeight);
+
+			super.setOnTouchListener(new OnTouchListener() {
+				@Override
+				public boolean onTouch(View v, MotionEvent event) {
+					if(customOnTouchListener != null) {
+						customOnTouchListener.onTouch(v, event);
+					}
+					return gestureImageViewTouchListener.onTouch(v, event);
+				}
+			});
+
+			layout = true;
+		}
+	}
+
+	protected void computeCropScale(int imageWidth, int imageHeight, int measuredWidth, int measuredHeight) {
+		fitScaleHorizontal = (float) measuredWidth / (float) imageWidth;
+		fitScaleVertical = (float) measuredHeight / (float) imageHeight;
+	}
+
+	protected void computeStartingScale(int imageWidth, int imageHeight, int measuredWidth, int measuredHeight) {
+		switch(getScaleType()) {
+			case CENTER:
+				// Center the image in the view, but perform no scaling.
+				startingScale = 1.0f;
+				break;
+
+			case CENTER_CROP:
+				startingScale = Math.max((float) measuredHeight / (float) imageHeight, (float) measuredWidth/ (float) imageWidth);
+				break;
+
+			case CENTER_INSIDE:
+				if(isLandscape()) {
+					startingScale = fitScaleHorizontal;
+				}
+				else {
+					startingScale = fitScaleVertical;
+				}
+				break;
+		}
+	}
+
+	protected boolean isRecycled() {
+		if(drawable != null && drawable instanceof BitmapDrawable) {
+			Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap();
+			if(bitmap != null) {
+				return bitmap.isRecycled();
+			}
+		}
+		return false;
+	}
+
+	protected void recycle() {
+		if(recycle && drawable != null && drawable instanceof BitmapDrawable) {
+			Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap();
+			if(bitmap != null) {
+				bitmap.recycle();
+			}
+		}
+	}
+
+	@Override
+	protected void onDraw(Canvas canvas) {
+		if(layout) {
+			if(drawable != null && !isRecycled()) {
+				canvas.save();
+
+				float adjustedScale = scale * scaleAdjust;
+
+				canvas.translate(x, y);
+
+				if(rotation != 0.0f) {
+					canvas.rotate(rotation);
+				}
+
+				if(adjustedScale != 1.0f) {
+					canvas.scale(adjustedScale, adjustedScale);
+				}
+
+				drawable.draw(canvas);
+
+				canvas.restore();
+			}
+
+			if(drawLock.availablePermits() <= 0) {
+				drawLock.release();
+			}
+		}
+	}
+
+	/**
+	 * Waits for a draw
+	 * @param max time to wait for draw (ms)
+	 * @throws InterruptedException
+	 */
+	public boolean waitForDraw(long timeout) throws InterruptedException {
+		return drawLock.tryAcquire(timeout, TimeUnit.MILLISECONDS);
+	}
+
+	@Override
+	protected void onAttachedToWindow() {
+		animator = new Animator(this, "GestureImageViewAnimator");
+		animator.start();
+
+		if(resId >= 0 && drawable == null) {
+			setImageResource(resId);
+		}
+
+		super.onAttachedToWindow();
+	}
+
+	public void animationStart(Animation animation) {
+		if(animator != null) {
+			animator.play(animation);
+		}
+	}
+
+	public void animationStop() {
+		if(animator != null) {
+			animator.cancel();
+		}
+	}
+
+	@Override
+	protected void onDetachedFromWindow() {
+		if(animator != null) {
+			animator.finish();
+		}
+		if(recycle && drawable != null && !isRecycled()) {
+			recycle();
+			drawable = null;
+		}
+		super.onDetachedFromWindow();
+	}
+
+	protected void initImage() {
+		if(this.drawable != null) {
+			this.drawable.setAlpha(alpha);
+			this.drawable.setFilterBitmap(true);
+			if(colorFilter != null) {
+				this.drawable.setColorFilter(colorFilter);
+			}
+		}
+
+		if(!layout) {
+			requestLayout();
+			redraw();
+		}
+	}
+
+	public void setImageBitmap(Bitmap image) {
+		this.drawable = new BitmapDrawable(getResources(), image);
+		initImage();
+	}
+
+	@Override
+	public void setImageDrawable(Drawable drawable) {
+		this.drawable = drawable;
+		initImage();
+	}
+
+	public void setImageResource(int id) {
+		if(this.drawable != null) {
+			this.recycle();
+		}
+		if(id >= 0) {
+			this.resId = id;
+			setImageDrawable(getContext().getResources().getDrawable(id));
+		}
+	}
+
+	public int getScaledWidth() {
+		return Math.round(getImageWidth() * getScale());
+	}
+
+	public int getScaledHeight() {
+		return Math.round(getImageHeight() * getScale());
+	}
+
+	public int getImageWidth() {
+		if(drawable != null) {
+			return drawable.getIntrinsicWidth();
+		}
+		return 0;
+	}
+
+	public int getImageHeight() {
+		if(drawable != null) {
+			return drawable.getIntrinsicHeight();
+		}
+		return 0;
+	}
+
+	public void moveBy(float x, float y) {
+		this.x += x;
+		this.y += y;
+	}
+
+	public void setPosition(float x, float y) {
+		this.x = x;
+		this.y = y;
+	}
+
+	public void redraw() {
+		postInvalidate();
+	}
+
+	public void setMinScale(float min) {
+		this.minScale = min;
+		if(gestureImageViewTouchListener != null) {
+			gestureImageViewTouchListener.setMinScale(min * fitScaleHorizontal);
+		}
+	}
+
+	public void setMaxScale(float max) {
+		this.maxScale = max;
+		if(gestureImageViewTouchListener != null) {
+			gestureImageViewTouchListener.setMaxScale(max * startingScale);
+		}
+	}
+
+	public void setScale(float scale) {
+		scaleAdjust = scale;
+	}
+
+	public float getScale() {
+		return scaleAdjust;
+	}
+
+	public float getImageX() {
+		return x;
+	}
+
+	public float getImageY() {
+		return y;
+	}
+
+	public boolean isStrict() {
+		return strict;
+	}
+
+	public void setStrict(boolean strict) {
+		this.strict = strict;
+	}
+
+	public boolean isRecycle() {
+		return recycle;
+	}
+
+	public void setRecycle(boolean recycle) {
+		this.recycle = recycle;
+	}
+
+	public void reset() {
+		x = centerX;
+		y = centerY;
+		scaleAdjust = startingScale;
+		redraw();
+	}
+
+	public void setRotation(float rotation) {
+		this.rotation = rotation;
+	}
+
+	public void setGestureImageViewListener(GestureImageViewListener pinchImageViewListener) {
+		this.gestureImageViewListener = pinchImageViewListener;
+	}
+
+	public GestureImageViewListener getGestureImageViewListener() {
+		return gestureImageViewListener;
+	}
+
+	@Override
+	public Drawable getDrawable() {
+		return drawable;
+	}
+
+	@Override
+	public void setAlpha(int alpha) {
+		this.alpha = alpha;
+		if(drawable != null) {
+			drawable.setAlpha(alpha);
+		}
+	}
+
+	@Override
+	public void setColorFilter(ColorFilter cf) {
+		this.colorFilter = cf;
+		if(drawable != null) {
+			drawable.setColorFilter(cf);
+		}
+	}
+
+	@Override
+	public void setImageURI(Uri mUri) {
+		if ("content".equals(mUri.getScheme())) {
+			try {
+				String[] orientationColumn = {MediaStore.Images.Media.ORIENTATION};
+
+				Cursor cur = getContext().getContentResolver().query(mUri, orientationColumn, null, null, null);
+
+				if (cur != null && cur.moveToFirst()) {
+					imageOrientation = cur.getInt(cur.getColumnIndex(orientationColumn[0]));
+				}
+
+				InputStream in = null;
+
+				try {
+					in = getContext().getContentResolver().openInputStream(mUri);
+					Bitmap bmp = BitmapFactory.decodeStream(in);
+
+					if(imageOrientation != 0) {
+						Matrix m = new Matrix();
+						m.postRotate(imageOrientation);
+						Bitmap rotated = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
+						bmp.recycle();
+						setImageDrawable(new BitmapDrawable(getResources(), rotated));
+					}
+					else {
+						setImageDrawable(new BitmapDrawable(getResources(), bmp));
+					}
+				}
+				finally {
+					if(in != null) {
+						in.close();
+					}
+
+					if(cur != null) {
+						cur.close();
+					}
+				}
+			}
+			catch (Exception e) {
+				Log.w("GestureImageView", "Unable to open content: " + mUri, e);
+			}
+		}
+		else {
+			setImageDrawable(Drawable.createFromPath(mUri.toString()));
+		}
+
+		if (drawable == null) {
+			Log.e("GestureImageView", "resolveUri failed on bad bitmap uri: " + mUri);
+			// Don't try again.
+			mUri = null;
+		}
+	}
+
+	@Override
+	public Matrix getImageMatrix() {
+		if(strict) {
+			throw new UnsupportedOperationException("Not supported");
+		}
+		return super.getImageMatrix();
+	}
+
+	@Override
+	public void setScaleType(ScaleType scaleType) {
+		if(scaleType == ScaleType.CENTER ||
+			scaleType == ScaleType.CENTER_CROP ||
+			scaleType == ScaleType.CENTER_INSIDE) {
+
+			super.setScaleType(scaleType);
+		}
+		else if(strict) {
+			throw new UnsupportedOperationException("Not supported");
+		}
+	}
+
+	@Override
+	public void invalidateDrawable(Drawable dr) {
+		if(strict) {
+			throw new UnsupportedOperationException("Not supported");
+		}
+		super.invalidateDrawable(dr);
+	}
+
+	@Override
+	public int[] onCreateDrawableState(int extraSpace) {
+		if(strict) {
+			throw new UnsupportedOperationException("Not supported");
+		}
+		return super.onCreateDrawableState(extraSpace);
+	}
+
+	@Override
+	public void setAdjustViewBounds(boolean adjustViewBounds) {
+		if(strict) {
+			throw new UnsupportedOperationException("Not supported");
+		}
+		super.setAdjustViewBounds(adjustViewBounds);
+	}
+
+	@Override
+	public void setImageLevel(int level) {
+		if(strict) {
+			throw new UnsupportedOperationException("Not supported");
+		}
+		super.setImageLevel(level);
+	}
+
+	@Override
+	public void setImageMatrix(Matrix matrix) {
+		if(strict) {
+			throw new UnsupportedOperationException("Not supported");
+		}
+	}
+
+	@Override
+	public void setImageState(int[] state, boolean merge) {
+		if(strict) {
+			throw new UnsupportedOperationException("Not supported");
+		}
+	}
+
+	@Override
+	public void setSelected(boolean selected) {
+		if(strict) {
+			throw new UnsupportedOperationException("Not supported");
+		}
+		super.setSelected(selected);
+	}
+
+	@Override
+	public void setOnTouchListener(OnTouchListener l) {
+		this.customOnTouchListener = l;
+	}
+
+	public float getCenterX() {
+		return centerX;
+	}
+
+	public float getCenterY() {
+		return centerY;
+	}
+
+	public boolean isLandscape() {
+		return getImageWidth() >= getImageHeight();
+	}
+
+	public boolean isPortrait() {
+		return getImageWidth() <= getImageHeight();
+	}
+
+	public void setStartingScale(float startingScale) {
+		this.startingScale = startingScale;
+	}
+
+	public void setStartingPosition(float x, float y) {
+		this.startX = x;
+		this.startY = y;
+	}
+
+	@Override
+	public void setOnClickListener(OnClickListener l) {
+		this.onClickListener = l;
+
+		if(gestureImageViewTouchListener != null) {
+			gestureImageViewTouchListener.setOnClickListener(l);
+		}
+	}
+
+	/**
+	 * Returns true if the image dimensions are aligned with the orientation of the device.
+	 * @return
+	 */
+	public boolean isOrientationAligned() {
+		if(deviceOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+			return isLandscape();
+		}
+		else if(deviceOrientation == Configuration.ORIENTATION_PORTRAIT) {
+			return isPortrait();
+		}
+		return true;
+	}
+
+	public int getDeviceOrientation() {
+		return deviceOrientation;
+	}
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewListener.java
new file mode 100644
index 0000000..4a52358
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+/**
+ * @author jasonpolites
+ *
+ */
+public interface GestureImageViewListener {
+
+	public void onTouch(float x, float y);
+
+	public void onScale(float scale);
+
+	public void onPosition(float x, float y);
+
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewTouchListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewTouchListener.java
new file mode 100644
index 0000000..76751d1
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewTouchListener.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import android.content.res.Configuration;
+import android.graphics.PointF;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+
+public class GestureImageViewTouchListener implements OnTouchListener {
+
+	private GestureImageView image;
+	private OnClickListener onClickListener;
+
+	private final PointF current = new PointF();
+	private final PointF last = new PointF();
+	private final PointF next = new PointF();
+	private final PointF midpoint = new PointF();
+
+	private final VectorF scaleVector = new VectorF();
+	private final VectorF pinchVector = new VectorF();
+
+	private boolean touched = false;
+	private boolean inZoom = false;
+
+	private float initialDistance;
+	private float lastScale = 1.0f;
+	private float currentScale = 1.0f;
+
+	private float boundaryLeft = 0;
+	private float boundaryTop = 0;
+	private float boundaryRight = 0;
+	private float boundaryBottom = 0;
+
+	private float maxScale = 5.0f;
+	private float minScale = 0.25f;
+	private float fitScaleHorizontal = 1.0f;
+	private float fitScaleVertical = 1.0f;
+
+	private int canvasWidth = 0;
+	private int canvasHeight = 0;
+
+	private float centerX = 0;
+	private float centerY = 0;
+
+	private float startingScale = 0;
+
+	private boolean canDragX = false;
+	private boolean canDragY = false;
+
+	private boolean multiTouch = false;
+
+	private int displayWidth;
+	private int displayHeight;
+
+	private int imageWidth;
+	private int imageHeight;
+
+	private FlingListener flingListener;
+	private FlingAnimation flingAnimation;
+	private ZoomAnimation zoomAnimation;
+	private MoveAnimation moveAnimation;
+	private GestureDetector tapDetector;
+	private GestureDetector flingDetector;
+	private GestureImageViewListener imageListener;
+
+	public GestureImageViewTouchListener(final GestureImageView image, int displayWidth, int displayHeight) {
+		super();
+
+		this.image = image;
+
+		this.displayWidth = displayWidth;
+		this.displayHeight = displayHeight;
+
+		this.centerX = (float) displayWidth / 2.0f;
+		this.centerY = (float) displayHeight / 2.0f;
+
+		this.imageWidth = image.getImageWidth();
+		this.imageHeight = image.getImageHeight();
+
+		startingScale = image.getScale();
+
+		currentScale = startingScale;
+		lastScale = startingScale;
+
+		boundaryRight = displayWidth;
+		boundaryBottom = displayHeight;
+		boundaryLeft = 0;
+		boundaryTop = 0;
+
+		next.x = image.getImageX();
+		next.y = image.getImageY();
+
+		flingListener = new FlingListener();
+		flingAnimation = new FlingAnimation();
+		zoomAnimation = new ZoomAnimation();
+		moveAnimation = new MoveAnimation();
+
+		flingAnimation.setListener(new FlingAnimationListener() {
+			@Override
+			public void onMove(float x, float y) {
+				handleDrag(current.x + x, current.y + y);
+			}
+
+			@Override
+			public void onComplete() {}
+		});
+
+		zoomAnimation.setZoom(2.0f);
+		zoomAnimation.setZoomAnimationListener(new ZoomAnimationListener() {
+			@Override
+			public void onZoom(float scale, float x, float y) {
+				if(scale <= maxScale && scale >= minScale) {
+					handleScale(scale, x, y);
+				}
+			}
+
+			@Override
+			public void onComplete() {
+				inZoom = false;
+				handleUp();
+			}
+		});
+
+		moveAnimation.setMoveAnimationListener(new MoveAnimationListener() {
+
+			@Override
+			public void onMove(float x, float y) {
+				image.setPosition(x, y);
+				image.redraw();
+			}
+		});
+
+		tapDetector = new GestureDetector(image.getContext(), new SimpleOnGestureListener() {
+			@Override
+			public boolean onDoubleTap(MotionEvent e) {
+				startZoom(e);
+				return true;
+			}
+
+			@Override
+			public boolean onSingleTapConfirmed(MotionEvent e) {
+				if(!inZoom) {
+					if(onClickListener != null) {
+						onClickListener.onClick(image);
+						return true;
+					}
+				}
+
+				return false;
+			}
+		});
+
+		flingDetector = new GestureDetector(image.getContext(), flingListener);
+		imageListener = image.getGestureImageViewListener();
+
+		calculateBoundaries();
+	}
+
+	private void startFling() {
+		flingAnimation.setVelocityX(flingListener.getVelocityX());
+		flingAnimation.setVelocityY(flingListener.getVelocityY());
+		image.animationStart(flingAnimation);
+	}
+
+	private void startZoom(MotionEvent e) {
+		inZoom = true;
+		zoomAnimation.reset();
+
+		float zoomTo = 1.0f;
+
+		if(image.isLandscape()) {
+			if(image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {
+				int scaledHeight = image.getScaledHeight();
+
+				if(scaledHeight < canvasHeight) {
+					zoomTo = fitScaleVertical / currentScale;
+					zoomAnimation.setTouchX(e.getX());
+					zoomAnimation.setTouchY(image.getCenterY());
+				}
+				else {
+					zoomTo = fitScaleHorizontal / currentScale;
+					zoomAnimation.setTouchX(image.getCenterX());
+					zoomAnimation.setTouchY(image.getCenterY());
+				}
+			}
+			else {
+				int scaledWidth = image.getScaledWidth();
+
+				if(scaledWidth == canvasWidth) {
+					zoomTo = currentScale*4.0f;
+					zoomAnimation.setTouchX(e.getX());
+					zoomAnimation.setTouchY(e.getY());
+				}
+				else if(scaledWidth < canvasWidth) {
+					zoomTo = fitScaleHorizontal / currentScale;
+					zoomAnimation.setTouchX(image.getCenterX());
+					zoomAnimation.setTouchY(e.getY());
+				}
+				else {
+					zoomTo = fitScaleHorizontal / currentScale;
+					zoomAnimation.setTouchX(image.getCenterX());
+					zoomAnimation.setTouchY(image.getCenterY());
+				}
+			}
+		}
+		else {
+			if(image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {
+
+				int scaledHeight = image.getScaledHeight();
+
+				if(scaledHeight == canvasHeight) {
+					zoomTo = currentScale*4.0f;
+					zoomAnimation.setTouchX(e.getX());
+					zoomAnimation.setTouchY(e.getY());
+				}
+				else if(scaledHeight < canvasHeight) {
+					zoomTo = fitScaleVertical / currentScale;
+					zoomAnimation.setTouchX(e.getX());
+					zoomAnimation.setTouchY(image.getCenterY());
+				}
+				else {
+					zoomTo = fitScaleVertical / currentScale;
+					zoomAnimation.setTouchX(image.getCenterX());
+					zoomAnimation.setTouchY(image.getCenterY());
+				}
+			}
+			else {
+				int scaledWidth = image.getScaledWidth();
+
+				if(scaledWidth < canvasWidth) {
+					zoomTo = fitScaleHorizontal / currentScale;
+					zoomAnimation.setTouchX(image.getCenterX());
+					zoomAnimation.setTouchY(e.getY());
+				}
+				else {
+					zoomTo = fitScaleVertical / currentScale;
+					zoomAnimation.setTouchX(image.getCenterX());
+					zoomAnimation.setTouchY(image.getCenterY());
+				}
+			}
+		}
+
+		zoomAnimation.setZoom(zoomTo);
+		image.animationStart(zoomAnimation);
+	}
+
+
+	private void stopAnimations() {
+		image.animationStop();
+	}
+
+	@Override
+	public boolean onTouch(View v, MotionEvent event) {
+
+		if(!inZoom) {
+
+			if(!tapDetector.onTouchEvent(event)) {
+				if(event.getPointerCount() == 1 && flingDetector.onTouchEvent(event)) {
+					startFling();
+				}
+
+				if(event.getAction() == MotionEvent.ACTION_UP) {
+					handleUp();
+				}
+				else if(event.getAction() == MotionEvent.ACTION_DOWN) {
+					stopAnimations();
+
+					last.x = event.getX();
+					last.y = event.getY();
+
+					if(imageListener != null) {
+						imageListener.onTouch(last.x, last.y);
+					}
+
+					touched = true;
+				}
+				else if(event.getAction() == MotionEvent.ACTION_MOVE) {
+					if(event.getPointerCount() > 1) {
+						multiTouch = true;
+						if(initialDistance > 0) {
+
+							pinchVector.set(event);
+							pinchVector.calculateLength();
+
+							float distance = pinchVector.length;
+
+							if(initialDistance != distance) {
+
+								float newScale = (distance / initialDistance) * lastScale;
+
+								if(newScale <= maxScale) {
+									scaleVector.length *= newScale;
+
+									scaleVector.calculateEndPoint();
+
+									scaleVector.length /= newScale;
+
+									float newX = scaleVector.end.x;
+									float newY = scaleVector.end.y;
+
+									handleScale(newScale, newX, newY);
+								}
+							}
+						}
+						else {
+							initialDistance = MathUtils.distance(event);
+
+							MathUtils.midpoint(event, midpoint);
+
+							scaleVector.setStart(midpoint);
+							scaleVector.setEnd(next);
+
+							scaleVector.calculateLength();
+							scaleVector.calculateAngle();
+
+							scaleVector.length /= lastScale;
+						}
+					}
+					else {
+						if(!touched) {
+							touched = true;
+							last.x = event.getX();
+							last.y = event.getY();
+							next.x = image.getImageX();
+							next.y = image.getImageY();
+						}
+						else if(!multiTouch) {
+							if(handleDrag(event.getX(), event.getY())) {
+								image.redraw();
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return true;
+	}
+
+	protected void handleUp() {
+
+		multiTouch = false;
+
+		initialDistance = 0;
+		lastScale = currentScale;
+
+		if(!canDragX) {
+			next.x = centerX;
+		}
+
+		if(!canDragY) {
+			next.y = centerY;
+		}
+
+		boundCoordinates();
+
+		if(!canDragX && !canDragY) {
+
+			if(image.isLandscape()) {
+				currentScale = fitScaleHorizontal;
+				lastScale = fitScaleHorizontal;
+			}
+			else {
+				currentScale = fitScaleVertical;
+				lastScale = fitScaleVertical;
+			}
+		}
+
+		image.setScale(currentScale);
+		image.setPosition(next.x, next.y);
+
+		if(imageListener != null) {
+			imageListener.onScale(currentScale);
+			imageListener.onPosition(next.x, next.y);
+		}
+
+		image.redraw();
+	}
+
+	protected void handleScale(float scale, float x, float y) {
+
+		currentScale = scale;
+
+		if(currentScale > maxScale) {
+			currentScale = maxScale;
+		}
+		else if (currentScale < minScale) {
+			currentScale = minScale;
+		}
+		else {
+			next.x = x;
+			next.y = y;
+		}
+
+		calculateBoundaries();
+
+		image.setScale(currentScale);
+		image.setPosition(next.x, next.y);
+
+		if(imageListener != null) {
+			imageListener.onScale(currentScale);
+			imageListener.onPosition(next.x, next.y);
+		}
+
+		image.redraw();
+	}
+
+	protected boolean handleDrag(float x, float y) {
+		current.x = x;
+		current.y = y;
+
+		float diffX = (current.x - last.x);
+		float diffY = (current.y - last.y);
+
+		if(diffX != 0 || diffY != 0) {
+
+			if(canDragX) next.x += diffX;
+			if(canDragY) next.y += diffY;
+
+			boundCoordinates();
+
+			last.x = current.x;
+			last.y = current.y;
+
+			if(canDragX || canDragY) {
+				image.setPosition(next.x, next.y);
+
+				if(imageListener != null) {
+					imageListener.onPosition(next.x, next.y);
+				}
+
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	public void reset() {
+		currentScale = startingScale;
+		next.x = centerX;
+		next.y = centerY;
+		calculateBoundaries();
+		image.setScale(currentScale);
+		image.setPosition(next.x, next.y);
+		image.redraw();
+	}
+
+
+	public float getMaxScale() {
+		return maxScale;
+	}
+
+	public void setMaxScale(float maxScale) {
+		this.maxScale = maxScale;
+	}
+
+	public float getMinScale() {
+		return minScale;
+	}
+
+	public void setMinScale(float minScale) {
+		this.minScale = minScale;
+	}
+
+	public void setOnClickListener(OnClickListener onClickListener) {
+		this.onClickListener = onClickListener;
+	}
+
+	protected void setCanvasWidth(int canvasWidth) {
+		this.canvasWidth = canvasWidth;
+	}
+
+	protected void setCanvasHeight(int canvasHeight) {
+		this.canvasHeight = canvasHeight;
+	}
+
+	protected void setFitScaleHorizontal(float fitScale) {
+		this.fitScaleHorizontal = fitScale;
+	}
+
+	protected void setFitScaleVertical(float fitScaleVertical) {
+		this.fitScaleVertical = fitScaleVertical;
+	}
+
+	protected void boundCoordinates() {
+		if(next.x < boundaryLeft) {
+			next.x = boundaryLeft;
+		}
+		else if(next.x > boundaryRight) {
+			next.x = boundaryRight;
+		}
+
+		if(next.y < boundaryTop) {
+			next.y = boundaryTop;
+		}
+		else if(next.y > boundaryBottom) {
+			next.y = boundaryBottom;
+		}
+	}
+
+	protected void calculateBoundaries() {
+
+		int effectiveWidth = Math.round( (float) imageWidth * currentScale );
+		int effectiveHeight = Math.round( (float) imageHeight * currentScale );
+
+		canDragX = effectiveWidth > displayWidth;
+		canDragY = effectiveHeight > displayHeight;
+
+		if(canDragX) {
+			float diff = (float)(effectiveWidth - displayWidth) / 2.0f;
+			boundaryLeft = centerX - diff;
+			boundaryRight = centerX + diff;
+		}
+
+		if(canDragY) {
+			float diff = (float)(effectiveHeight - displayHeight) / 2.0f;
+			boundaryTop = centerY - diff;
+			boundaryBottom = centerY + diff;
+		}
+	}
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/MathUtils.java b/android/experimental/LibreOffice4Android/src/com/polites/android/MathUtils.java
new file mode 100644
index 0000000..df7f30d
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/MathUtils.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import android.graphics.PointF;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+
+public class MathUtils {
+
+	public static float distance(MotionEvent event) {
+		float x = event.getX(0) - event.getX(1);
+		float y = event.getY(0) - event.getY(1);
+		return FloatMath.sqrt(x * x + y * y);
+	}
+
+	public static float distance(PointF p1, PointF p2) {
+		float x = p1.x - p2.x;
+		float y = p1.y - p2.y;
+		return FloatMath.sqrt(x * x + y * y);
+	}
+
+	public static float distance(float x1, float y1, float x2, float y2) {
+		float x = x1 - x2;
+		float y = y1 - y2;
+		return FloatMath.sqrt(x * x + y * y);
+	}
+
+	public static void midpoint(MotionEvent event, PointF point) {
+		float x1 = event.getX(0);
+		float y1 = event.getY(0);
+		float x2 = event.getX(1);
+		float y2 = event.getY(1);
+		midpoint(x1, y1, x2, y2, point);
+	}
+
+	public static void midpoint(float x1, float y1, float x2, float y2, PointF point) {
+		point.x = (x1 + x2) / 2.0f;
+		point.y = (y1 + y2) / 2.0f;
+	}
+	/**
+	 * Rotates p1 around p2 by angle degrees.
+	 * @param p1
+	 * @param p2
+	 * @param angle
+	 */
+	public void rotate(PointF p1, PointF p2, float angle) {
+		float px = p1.x;
+		float py = p1.y;
+		float ox = p2.x;
+		float oy = p2.y;
+		p1.x = (FloatMath.cos(angle) * (px-ox) - FloatMath.sin(angle) * (py-oy) + ox);
+		p1.y = (FloatMath.sin(angle) * (px-ox) + FloatMath.cos(angle) * (py-oy) + oy);
+	}
+
+	public static float angle(PointF p1, PointF p2) {
+		return angle(p1.x, p1.y, p2.x, p2.y);
+	}
+
+	public static float angle(float x1, float y1, float x2, float y2) {
+		return (float) Math.atan2(y2 - y1, x2 - x1);
+	}
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimation.java b/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimation.java
new file mode 100644
index 0000000..5303d64
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimation.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public class MoveAnimation implements Animation {
+
+	private boolean firstFrame = true;
+
+	private float startX;
+	private float startY;
+
+	private float targetX;
+	private float targetY;
+	private long animationTimeMS = 100;
+	private long totalTime = 0;
+
+	private MoveAnimationListener moveAnimationListener;
+
+	/* (non-Javadoc)
+	 * @see com.polites.android.Animation#update(com.polites.android.GestureImageView, long)
+	 */
+	@Override
+	public boolean update(GestureImageView view, long time) {
+		totalTime += time;
+
+		if(firstFrame) {
+			firstFrame = false;
+			startX = view.getImageX();
+			startY = view.getImageY();
+		}
+
+		if(totalTime < animationTimeMS) {
+
+			float ratio = (float) totalTime / animationTimeMS;
+
+			float newX = ((targetX - startX) * ratio) + startX;
+			float newY = ((targetY - startY) * ratio) + startY;
+
+			if(moveAnimationListener != null) {
+				moveAnimationListener.onMove(newX, newY);
+			}
+
+			return true;
+		}
+		else {
+			if(moveAnimationListener != null) {
+				moveAnimationListener.onMove(targetX, targetY);
+			}
+		}
+
+		return false;
+	}
+
+	public void reset() {
+		firstFrame = true;
+		totalTime = 0;
+	}
+
+
+	public float getTargetX() {
+		return targetX;
+	}
+
+
+	public void setTargetX(float targetX) {
+		this.targetX = targetX;
+	}
+
+
+	public float getTargetY() {
+		return targetY;
+	}
+
+	public void setTargetY(float targetY) {
+		this.targetY = targetY;
+	}
+
+	public long getAnimationTimeMS() {
+		return animationTimeMS;
+	}
+
+	public void setAnimationTimeMS(long animationTimeMS) {
+		this.animationTimeMS = animationTimeMS;
+	}
+
+	public void setMoveAnimationListener(MoveAnimationListener moveAnimationListener) {
+		this.moveAnimationListener = moveAnimationListener;
+	}
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimationListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimationListener.java
new file mode 100644
index 0000000..a19a265
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimationListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public interface MoveAnimationListener {
+
+	public void onMove(float x, float y);
+
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/VectorF.java b/android/experimental/LibreOffice4Android/src/com/polites/android/VectorF.java
new file mode 100644
index 0000000..1ff4b19
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/VectorF.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import android.graphics.PointF;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+
+public class VectorF {
+
+	public float angle;
+	public float length;
+
+	public final PointF start = new PointF();
+	public final PointF end = new PointF();
+
+	public void calculateEndPoint() {
+		end.x = FloatMath.cos(angle) * length + start.x;
+		end.y = FloatMath.sin(angle) * length + start.y;
+	}
+
+	public void setStart(PointF p) {
+		this.start.x = p.x;
+		this.start.y = p.y;
+	}
+
+	public void setEnd(PointF p) {
+		this.end.x = p.x;
+		this.end.y = p.y;
+	}
+
+	public void set(MotionEvent event) {
+		this.start.x = event.getX(0);
+		this.start.y = event.getY(0);
+		this.end.x = event.getX(1);
+		this.end.y = event.getY(1);
+	}
+
+	public float calculateLength() {
+		length = MathUtils.distance(start, end);
+		return length;
+	}
+
+	public float calculateAngle() {
+		angle = MathUtils.angle(start, end);
+		return angle;
+	}
+
+
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimation.java b/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimation.java
new file mode 100644
index 0000000..673b7f9
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimation.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import android.graphics.PointF;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public class ZoomAnimation implements Animation {
+
+	private boolean firstFrame = true;
+
+	private float touchX;
+	private float touchY;
+
+	private float zoom;
+
+	private float startX;
+	private float startY;
+	private float startScale;
+
+	private float xDiff;
+	private float yDiff;
+	private float scaleDiff;
+
+	private long animationLengthMS = 200;
+	private long totalTime = 0;
+
+	private ZoomAnimationListener zoomAnimationListener;
+
+	/* (non-Javadoc)
+	 * @see com.polites.android.Animation#update(com.polites.android.GestureImageView, long)
+	 */
+	@Override
+	public boolean update(GestureImageView view, long time) {
+		if(firstFrame) {
+			firstFrame = false;
+
+			startX = view.getImageX();
+			startY = view.getImageY();
+			startScale = view.getScale();
+			scaleDiff = (zoom * startScale) - startScale;
+
+			if(scaleDiff > 0) {
+				// Calculate destination for midpoint
+				VectorF vector = new VectorF();
+
+				// Set the touch point as start because we want to move the end
+				vector.setStart(new PointF(touchX, touchY));
+				vector.setEnd(new PointF(startX, startY));
+
+				vector.calculateAngle();
+
+				// Get the current length
+				float length = vector.calculateLength();
+
+				// Multiply length by zoom to get the new length
+				vector.length = length*zoom;
+
+				// Now deduce the new endpoint
+				vector.calculateEndPoint();
+
+				xDiff = vector.end.x - startX;
+				yDiff = vector.end.y - startY;
+			}
+			else {
+				// Zoom out to center
+				xDiff = view.getCenterX() - startX;
+				yDiff = view.getCenterY() - startY;
+			}
+		}
+
+		totalTime += time;
+
+		float ratio = (float) totalTime / (float) animationLengthMS;
+
+		if(ratio < 1) {
+
+			if(ratio > 0) {
+				// we still have time left
+				float newScale = (ratio * scaleDiff) + startScale;
+				float newX = (ratio * xDiff) + startX;
+				float newY = (ratio * yDiff) + startY;
+
+				if(zoomAnimationListener != null) {
+					zoomAnimationListener.onZoom(newScale, newX, newY);
+				}
+			}
+
+			return true;
+		}
+		else {
+
+			float newScale = scaleDiff + startScale;
+			float newX = xDiff + startX;
+			float newY = yDiff + startY;
+
+			if(zoomAnimationListener != null) {
+				zoomAnimationListener.onZoom(newScale, newX, newY);
+				zoomAnimationListener.onComplete();
+			}
+
+			return false;
+		}
+	}
+
+	public void reset() {
+		firstFrame = true;
+		totalTime = 0;
+	}
+
+	public float getZoom() {
+		return zoom;
+	}
+
+	public void setZoom(float zoom) {
+		this.zoom = zoom;
+	}
+
+	public float getTouchX() {
+		return touchX;
+	}
+
+	public void setTouchX(float touchX) {
+		this.touchX = touchX;
+	}
+
+	public float getTouchY() {
+		return touchY;
+	}
+
+	public void setTouchY(float touchY) {
+		this.touchY = touchY;
+	}
+
+	public long getAnimationLengthMS() {
+		return animationLengthMS;
+	}
+
+	public void setAnimationLengthMS(long animationLengthMS) {
+		this.animationLengthMS = animationLengthMS;
+	}
+
+	public ZoomAnimationListener getZoomAnimationListener() {
+		return zoomAnimationListener;
+	}
+
+	public void setZoomAnimationListener(ZoomAnimationListener zoomAnimationListener) {
+		this.zoomAnimationListener = zoomAnimationListener;
+	}
+}
diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimationListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimationListener.java
new file mode 100644
index 0000000..8df4bf6
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimationListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public interface ZoomAnimationListener {
+	public void onZoom(float scale, float x, float y);
+	public void onComplete();
+}
diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/android/examples/DocumentLoader.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/android/examples/DocumentLoader.java
new file mode 100644
index 0000000..cf042a4
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/android/examples/DocumentLoader.java
@@ -0,0 +1,596 @@
+// -*- Mode: Java; 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/.
+
+// This is just a testbed for ideas and implementations. (Still, it might turn
+// out to be somewhat useful as such while waiting for "real" apps.)
+
+// Important points:
+
+// Everything that might take a long time should be done asynchronously:
+//  - loading the document (loadComponentFromURL())
+//  - counting number of pages (getRendererCount())
+//  - rendering a page (render())
+
+// Unclear whether pages can be rendered in parallel. Probably best to
+// serialize all the above in the same worker thread, for instance using
+// AsyncTask.SERIAL_EXECUTOR.
+
+// While a page is loading ideally should display some animated spinner (but
+// for now just a static "please wait" text).
+
+// Just three views are used for the pages: For the current page being viewed,
+// the previous, and the next. This could be bumped higher, need to make the
+// "3" into a parameter below.
+
+package org.libreoffice.android.examples;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.TranslateAnimation;

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list