summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIain Billett <iainbillett@gmail.com>2012-06-22 17:32:08 +0100
committerIain Billett <iainbillett@gmail.com>2012-06-22 17:33:51 +0100
commitd3949d453d06bbdd88d33dd0d6e660f67fad546a (patch)
treef415a328321577ba4eb029050c0d8ed09d7fc969
parentceb8b18f5b7437ba7438c428c3c78e4d8d67fee3 (diff)
An new project to combine DocumentLoader with the Android UI. (Not building - see manifest)
-rw-r--r--android/experimental/LibreOffice4Android/AndroidManifest.xml40
-rw-r--r--android/experimental/LibreOffice4Android/Makefile272
-rw-r--r--android/experimental/LibreOffice4Android/build.xml114
-rw-r--r--android/experimental/LibreOffice4Android/fonts.conf154
-rw-r--r--android/experimental/LibreOffice4Android/jni/Android.mk8
-rw-r--r--android/experimental/LibreOffice4Android/jni/Application.mk3
-rw-r--r--android/experimental/LibreOffice4Android/project.properties14
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/action_search.pngbin0 -> 1759 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/base.pngbin0 -> 20684 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/calc.pngbin0 -> 20697 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/draw.pngbin0 -> 18445 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/dummy_page.pngbin0 -> 77517 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/folder.pngbin0 -> 17650 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/ic_launcher.pngbin0 -> 4147 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/impress.pngbin0 -> 13936 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/light_sort_by_size.pngbin0 -> 1141 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_grid.pngbin0 -> 1286 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_list.pngbin0 -> 1363 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/lo_icon.pngbin0 -> 4475 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/main.pngbin0 -> 4380 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/math.pngbin0 -> 15370 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/startcenter.pngbin0 -> 4380 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-hdpi/writer.pngbin0 -> 19535 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-ldpi/dummy_page.pngbin0 -> 77517 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-ldpi/ic_launcher.pngbin0 -> 1723 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-ldpi/lo_icon.pngbin0 -> 2773 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-mdpi/ic_launcher.pngbin0 -> 2574 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/drawable-mdpi/lo_icon.pngbin0 -> 3255 bytes
-rw-r--r--android/experimental/LibreOffice4Android/res/layout/file_explorer_grid_item.xml29
-rw-r--r--android/experimental/LibreOffice4Android/res/layout/file_grid.xml20
-rw-r--r--android/experimental/LibreOffice4Android/res/layout/file_list.xml13
-rw-r--r--android/experimental/LibreOffice4Android/res/layout/file_list_item.xml41
-rw-r--r--android/experimental/LibreOffice4Android/res/layout/main.xml15
-rw-r--r--android/experimental/LibreOffice4Android/res/menu/view_menu.xml20
-rw-r--r--android/experimental/LibreOffice4Android/res/values/arrays.xml56
-rw-r--r--android/experimental/LibreOffice4Android/res/values/strings.xml16
-rw-r--r--android/experimental/LibreOffice4Android/res/xml/libreoffice_preferences.xml21
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/Animation.java32
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/Animator.java96
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimation.java74
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimationListener.java29
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/FlingListener.java45
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageView.java712
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewListener.java30
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewTouchListener.java540
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/MathUtils.java76
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimation.java107
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimationListener.java27
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/VectorF.java63
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimation.java167
-rw-r--r--android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimationListener.java26
-rw-r--r--android/experimental/LibreOffice4Android/src/org/libreoffice/android/examples/DocumentLoader.java596
-rw-r--r--android/experimental/LibreOffice4Android/src/org/libreoffice/ui/FileUtilities.java159
-rw-r--r--android/experimental/LibreOffice4Android/src/org/libreoffice/ui/GridItemAdapter.java95
-rw-r--r--android/experimental/LibreOffice4Android/src/org/libreoffice/ui/LibreOfficeUIActivity.java548
-rw-r--r--android/experimental/LibreOffice4Android/src/org/libreoffice/ui/ListItemAdapter.java159
-rw-r--r--android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PageView.java63
-rw-r--r--android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PreferenceEditor.java18
-rw-r--r--android/experimental/LibreOffice4Android/src/org/libreoffice/ui/WriterViewerActivity.java37
59 files changed, 4535 insertions, 0 deletions
diff --git a/android/experimental/LibreOffice4Android/AndroidManifest.xml b/android/experimental/LibreOffice4Android/AndroidManifest.xml
new file mode 100644
index 000000000000..f521536fae0e
--- /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 000000000000..94233369b0e2
--- /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 000000000000..5d960196c6b0
--- /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 000000000000..699e9d101048
--- /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 000000000000..939a1ea503bb
--- /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 000000000000..f326d1a59879
--- /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 000000000000..06b2d880c3d4
--- /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 000000000000..e6b70451863a
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/action_search.png
Binary files 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 000000000000..729dbcd82ebf
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/base.png
Binary files 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 000000000000..a3f5fd4d80c0
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/calc.png
Binary files 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 000000000000..b3ee11426a04
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/draw.png
Binary files 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 000000000000..c58d276e7085
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/dummy_page.png
Binary files 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 000000000000..9c9b42c83956
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/folder.png
Binary files 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 000000000000..8074c4c571b8
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/ic_launcher.png
Binary files 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 000000000000..5909f05bf089
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/impress.png
Binary files 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 000000000000..3b34aaf8ab57
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_sort_by_size.png
Binary files 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 000000000000..ae138edbf006
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_grid.png
Binary files 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 000000000000..c5f6c97b2687
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_list.png
Binary files 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 000000000000..2ef86417e69e
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/lo_icon.png
Binary files 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 000000000000..7e8e2a05e2da
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/main.png
Binary files 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 000000000000..50b8dc863bff
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/math.png
Binary files 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 000000000000..7e8e2a05e2da
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/startcenter.png
Binary files 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 000000000000..2f4abcb280cd
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/writer.png
Binary files 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 000000000000..c58d276e7085
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-ldpi/dummy_page.png
Binary files 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 000000000000..1095584ec21f
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-ldpi/ic_launcher.png
Binary files 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 000000000000..95b3113b6f95
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-ldpi/lo_icon.png
Binary files 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 000000000000..a07c69fa5a0f
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-mdpi/ic_launcher.png
Binary files 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 000000000000..4f3f89beadc2
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/res/drawable-mdpi/lo_icon.png
Binary files 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 000000000000..ce42e577fa95
--- /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 000000000000..1e241c00a13a
--- /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 000000000000..6ef02555a8a3
--- /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 000000000000..0bff445659a0
--- /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 000000000000..6b97fe101177
--- /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 000000000000..87270d3a5985
--- /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 000000000000..67a157d201bf
--- /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 000000000000..8951dd495b8a
--- /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 000000000000..d19d9e65298c
--- /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 000000000000..993620893f91
--- /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 000000000000..fb0728b7bf13
--- /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 000000000000..3124b6201464
--- /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 000000000000..b9611d51c040
--- /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 000000000000..ab3007a14b00
--- /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 000000000000..1cde6e4b4889
--- /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 000000000000..4a52358216d5
--- /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 000000000000..76751d145b58
--- /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 000000000000..df7f30db54a7
--- /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 000000000000..5303d646672b
--- /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 000000000000..a19a265e5844
--- /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 000000000000..1ff4b19d7e4f
--- /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 000000000000..673b7f9cb148
--- /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 000000000000..8df4bf641952
--- /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 000000000000..cf042a426421
--- /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;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.ViewFlipper;
+import android.widget.ViewSwitcher;
+
+import com.polites.android.GestureImageView;
+
+import com.sun.star.awt.Size;
+import com.sun.star.awt.XBitmap;
+import com.sun.star.awt.XControl;
+import com.sun.star.awt.XDevice;
+import com.sun.star.awt.XToolkit2;
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.frame.XComponentLoader;
+import com.sun.star.frame.XController;
+import com.sun.star.frame.XFrame;
+import com.sun.star.frame.XModel;
+import com.sun.star.lang.XEventListener;
+import com.sun.star.lang.XMultiComponentFactory;
+import com.sun.star.lang.XTypeProvider;
+import com.sun.star.uno.Type;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+import com.sun.star.view.XRenderable;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.libreoffice.android.Bootstrap;
+
+public class DocumentLoader
+ extends Activity
+{
+ private static final String TAG = "DocumentLoader";
+
+ // Size of a small virtual (bitmap) device used to find out page count and
+ // page sizes
+ private static final int SMALLSIZE = 128;
+
+ // We pre-render this many pages preceding and succeeding the currently
+ // viewed one, i.e. the total number of rendered pages kept is
+ // PAGECACHE_PLUSMINUS*2+1.
+ private static final int PAGECACHE_PLUSMINUS = 2;
+ private static final int PAGECACHE_SIZE = PAGECACHE_PLUSMINUS*2 + 1;
+
+ long timingOverhead;
+ XComponentContext context;
+ XMultiComponentFactory mcf;
+ XComponentLoader componentLoader;
+ XToolkit2 toolkit;
+ XDevice dummySmallDevice;
+ Object doc;
+ int pageCount;
+ XRenderable renderable;
+
+ GestureDetector gestureDetector;
+
+ ViewGroup.LayoutParams matchParent;
+
+ ViewFlipper flipper;
+
+ class GestureListener
+ extends GestureDetector.SimpleOnGestureListener
+ {
+ @Override
+ public boolean onFling(MotionEvent event1,
+ MotionEvent event2,
+ float velocityX,
+ float velocityY)
+ {
+ Log.i(TAG, "onFling: " + event1 + " " + event2);
+ if (event1.getX() - event2.getX() > 120) {
+ if (((PageViewer)flipper.getCurrentView()).currentPageNumber == pageCount-1)
+ return false;
+
+ Animation inFromRight = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0,
+ Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
+ inFromRight.setDuration(500);
+ flipper.setInAnimation(inFromRight);
+
+ Animation outToLeft = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, -1,
+ Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
+ outToLeft.setDuration(500);
+ flipper.setOutAnimation(outToLeft);
+
+ flipper.showNext();
+
+ ((PageViewer)flipper.getChildAt((flipper.getDisplayedChild() + PAGECACHE_PLUSMINUS) % PAGECACHE_SIZE)).display(((PageViewer)flipper.getCurrentView()).currentPageNumber + PAGECACHE_PLUSMINUS);
+ return true;
+ } else if (event2.getX() - event1.getX() > 120) {
+ if (((PageViewer)flipper.getCurrentView()).currentPageNumber == 0)
+ return false;
+
+ Animation inFromLeft = new TranslateAnimation(Animation.RELATIVE_TO_SELF, -1, Animation.RELATIVE_TO_SELF, 0,
+ Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
+ inFromLeft.setDuration(500);
+ flipper.setInAnimation(inFromLeft);
+
+ Animation outToRight = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1,
+ Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
+ outToRight.setDuration(500);
+ flipper.setOutAnimation(outToRight);
+
+ flipper.showPrevious();
+
+ ((PageViewer)flipper.getChildAt((flipper.getDisplayedChild() + PAGECACHE_SIZE - PAGECACHE_PLUSMINUS) % PAGECACHE_SIZE)).display(((PageViewer)flipper.getCurrentView()).currentPageNumber - PAGECACHE_PLUSMINUS);
+
+ return true;
+ }
+ return false;
+ }
+ }
+
+ class MyXController
+ implements XController
+ {
+
+ XFrame frame;
+ XModel model;
+
+ public void attachFrame(XFrame frame)
+ {
+ Log.i(TAG, "attachFrame");
+ this.frame = frame;
+ }
+
+ public boolean attachModel(XModel model)
+ {
+ Log.i(TAG, "attachModel");
+ this.model = model;
+ return true;
+ }
+
+ public boolean suspend(boolean doSuspend)
+ {
+ Log.i(TAG, "suspend");
+ return false;
+ }
+
+ public Object getViewData()
+ {
+ Log.i(TAG, "getViewData");
+ return null;
+ }
+
+ public void restoreViewData(Object data)
+ {
+ Log.i(TAG, "restoreViewData");
+ }
+
+ public XModel getModel()
+ {
+ Log.i(TAG, "getModel");
+ return model;
+ }
+
+ public XFrame getFrame()
+ {
+ Log.i(TAG, "getFrame");
+ return frame;
+ }
+
+ public void dispose()
+ {
+ Log.i(TAG, "dispose");
+ }
+
+ public void addEventListener(XEventListener listener)
+ {
+ Log.i(TAG, "addEventListener");
+ }
+
+ public void removeEventListener(XEventListener listener)
+ {
+ Log.i(TAG, "removeEventListener");
+ }
+ }
+
+ ByteBuffer renderPage(int number)
+ {
+ try {
+ // Use dummySmallDevice with no scale of offset just to find out
+ // the paper size of this page.
+
+ PropertyValue renderProps[] = new PropertyValue[3];
+ renderProps[0] = new PropertyValue();
+ renderProps[0].Name = "IsPrinter";
+ renderProps[0].Value = new Boolean(true);
+ renderProps[1] = new PropertyValue();
+ renderProps[1].Name = "RenderDevice";
+ renderProps[1].Value = dummySmallDevice;
+ renderProps[2] = new PropertyValue();
+ renderProps[2].Name = "View";
+ renderProps[2].Value = new MyXController();
+
+ // getRenderer returns a set of properties that include the PageSize
+ long t0 = System.currentTimeMillis();
+ PropertyValue rendererProps[] = renderable.getRenderer(number, doc, renderProps);
+ long t1 = System.currentTimeMillis();
+ Log.i(TAG, "getRenderer took " + ((t1-t0)-timingOverhead) + " ms");
+
+ int pageWidth = 0, pageHeight = 0;
+ for (int i = 0; i < rendererProps.length; i++) {
+ if (rendererProps[i].Name.equals("PageSize")) {
+ pageWidth = ((Size) rendererProps[i].Value).Width;
+ pageHeight = ((Size) rendererProps[i].Value).Height;
+ Log.i(TAG, "PageSize: " + pageWidth + "x" + pageHeight);
+ }
+ }
+
+ // Create a new device with the correct scale and offset
+ ByteBuffer bb = ByteBuffer.allocateDirect(flipper.getWidth()*flipper.getHeight()*4);
+ long wrapped_bb = Bootstrap.new_byte_buffer_wrapper(bb);
+
+ XDevice device;
+ if (pageWidth == 0) {
+ // Huh?
+ device = toolkit.createScreenCompatibleDeviceUsingBuffer(flipper.getWidth(), flipper.getHeight(), 1, 1, 0, 0, wrapped_bb);
+ } else {
+
+ // Scale so that it fits our device which has a resolution of 96/in (see
+ // SvpSalGraphics::GetResolution()). The page size returned from getRenderer() is in 1/mm * 100.
+
+ int scaleNumerator, scaleDenominator;
+
+ // If the view has a wider aspect ratio than the page, fit
+ // height; otherwise, fit width
+ if ((double) flipper.getWidth() / flipper.getHeight() > (double) pageWidth / pageHeight) {
+ scaleNumerator = flipper.getHeight();
+ scaleDenominator = pageHeight / 2540 * 96;
+ } else {
+ scaleNumerator = flipper.getWidth();
+ scaleDenominator = pageWidth / 2540 * 96;
+ }
+ Log.i(TAG, "Scaling with " + scaleNumerator + "/" + scaleDenominator);
+
+ device = toolkit.createScreenCompatibleDeviceUsingBuffer(flipper.getWidth(), flipper.getHeight(),
+ scaleNumerator, scaleDenominator,
+ 0, 0,
+ wrapped_bb);
+ }
+
+ // Update the property that points to the device
+ renderProps[1].Value = device;
+
+ t0 = System.currentTimeMillis();
+ renderable.render(number, doc, renderProps);
+ t1 = System.currentTimeMillis();
+ Log.i(TAG, "Rendering page " + number + " took " + ((t1-t0)-timingOverhead) + " ms");
+
+ Bootstrap.force_full_alpha_bb(bb, 0, flipper.getWidth() * flipper.getHeight() * 4);
+
+ return bb;
+ }
+ catch (Exception e) {
+ e.printStackTrace(System.err);
+ finish();
+ }
+
+ return null;
+ }
+
+ enum PageState { NONEXISTENT, LOADING, READY };
+
+ class PageViewer
+ extends ViewSwitcher
+ {
+ int currentPageNumber = -1;
+ TextView waitView;
+ PageState state = PageState.NONEXISTENT;
+ Bitmap bm;
+
+ class PageLoadTask
+ extends AsyncTask<Integer, Void, Integer>
+ {
+ protected Integer doInBackground(Integer... params)
+ {
+ int number = params[0];
+
+ if (number >= pageCount)
+ return -1;
+
+ state = PageState.LOADING;
+ currentPageNumber = number;
+ ByteBuffer bb = renderPage(currentPageNumber);
+ bm = Bitmap.createBitmap(flipper.getWidth(), flipper.getHeight(), Bitmap.Config.ARGB_8888);
+ bm.copyPixelsFromBuffer(bb);
+
+ return currentPageNumber;
+ }
+
+ protected void onPostExecute(Integer result)
+ {
+ Log.i(TAG, "onPostExecute: " + result);
+ if (result == -1)
+ return;
+
+ ImageView imageView = new ImageView(DocumentLoader.this);
+ imageView.setImageBitmap(bm);
+
+ imageView.setScaleY(-1);
+
+ if (getChildCount() == 2)
+ removeViewAt(1);
+ addView(imageView, 1, matchParent);
+ showNext();
+ state = PageState.READY;
+ }
+ }
+
+ void display(int number)
+ {
+ Log.i(TAG, "PageViewer display(" + number + ")");
+ if (number >= 0)
+ waitView.setText("Page " + (number+1) + ", wait...");
+ state = PageState.NONEXISTENT;
+
+ if (getDisplayedChild() == 1) {
+ showPrevious();
+ removeViewAt(1);
+ }
+
+ if (number >= 0) {
+ new PageLoadTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, number);
+ }
+ }
+
+ PageViewer(int number)
+ {
+ super(DocumentLoader.this);
+
+ waitView = new TextView(DocumentLoader.this);
+ waitView.setTextSize(24);
+ waitView.setGravity(Gravity.CENTER);
+ waitView.setBackgroundColor(Color.WHITE);
+ waitView.setTextColor(Color.BLACK);
+ addView(waitView, 0, matchParent);
+
+ display(number);
+ }
+ }
+
+ class DocumentLoadTask
+ extends AsyncTask<String, Void, Void>
+ {
+ protected Void doInBackground(String... params)
+ {
+ try {
+ String url = params[0];
+ Log.i(TAG, "Attempting to load " + url);
+
+ PropertyValue loadProps[] = new PropertyValue[3];
+ loadProps[0] = new PropertyValue();
+ loadProps[0].Name = "Hidden";
+ loadProps[0].Value = new Boolean(true);
+ loadProps[1] = new PropertyValue();
+ loadProps[1].Name = "ReadOnly";
+ loadProps[1].Value = new Boolean(true);
+ loadProps[2] = new PropertyValue();
+ loadProps[2].Name = "Preview";
+ loadProps[2].Value = new Boolean(true);
+
+ long t0 = System.currentTimeMillis();
+ doc = componentLoader.loadComponentFromURL(url, "_blank", 0, loadProps);
+ long t1 = System.currentTimeMillis();
+ Log.i(TAG, "Loading took " + ((t1-t0)-timingOverhead) + " ms");
+
+ Object toolkitService = mcf.createInstanceWithContext
+ ("com.sun.star.awt.Toolkit", context);
+ toolkit = (XToolkit2) UnoRuntime.queryInterface(XToolkit2.class, toolkitService);
+
+ renderable = (XRenderable) UnoRuntime.queryInterface(XRenderable.class, doc);
+
+ // Set up dummySmallDevice and use it to find out the number
+ // of pages ("renderers").
+ ByteBuffer smallbb = ByteBuffer.allocateDirect(SMALLSIZE*SMALLSIZE*4);
+ long wrapped_smallbb = Bootstrap.new_byte_buffer_wrapper(smallbb);
+ dummySmallDevice = toolkit.createScreenCompatibleDeviceUsingBuffer(SMALLSIZE, SMALLSIZE, 1, 1, 0, 0, wrapped_smallbb);
+
+ PropertyValue renderProps[] = new PropertyValue[3];
+ renderProps[0] = new PropertyValue();
+ renderProps[0].Name = "IsPrinter";
+ renderProps[0].Value = new Boolean(true);
+ renderProps[1] = new PropertyValue();
+ renderProps[1].Name = "RenderDevice";
+ renderProps[1].Value = dummySmallDevice;
+ renderProps[2] = new PropertyValue();
+ renderProps[2].Name = "View";
+ renderProps[2].Value = new MyXController();
+
+ t0 = System.currentTimeMillis();
+ pageCount = renderable.getRendererCount(doc, renderProps);
+ t1 = System.currentTimeMillis();
+ Log.i(TAG, "getRendererCount: " + pageCount + ", took " + ((t1-t0)-timingOverhead) + " ms");
+ }
+ catch (Exception e) {
+ e.printStackTrace(System.err);
+ finish();
+ }
+ return null;
+ }
+ }
+
+ static void dumpUNOObject(String objectName, Object object)
+ {
+ Log.i(TAG, objectName + " is " + (object != null ? object.toString() : "null"));
+
+ if (object == null)
+ return;
+
+ XTypeProvider typeProvider = (XTypeProvider)
+ UnoRuntime.queryInterface(XTypeProvider.class, object);
+ if (typeProvider == null)
+ return;
+
+ Type[] types = typeProvider.getTypes();
+ if (types == null)
+ return;
+
+ for (Type t : types)
+ Log.i(TAG, " " + t.getTypeName());
+ }
+
+ static void dumpBytes(String name, byte[] bytes, int offset)
+ {
+ if (bytes == null) {
+ Log.i(TAG, name + " is null");
+ return;
+ }
+ Log.i(TAG, name + ":");
+
+ if (offset != 0)
+ Log.i(TAG, " (offset " + offset + ")");
+
+ for (int i = offset; i < Math.min(bytes.length, offset+160); i += 16) {
+ String s = "";
+ for (int j = i; j < Math.min(bytes.length, i+16); j++)
+ s = s + String.format(" %02x", bytes[j]);
+
+ Log.i(TAG, s);
+ }
+ }
+
+ static void dumpBytes(String name, ByteBuffer bytes, int offset)
+ {
+ if (bytes == null) {
+ Log.i(TAG, name + " is null");
+ return;
+ }
+ Log.i(TAG, name + ":");
+
+ if (offset != 0)
+ Log.i(TAG, " (offset " + offset + ")");
+
+ for (int i = offset; i < Math.min(bytes.limit(), offset+160); i += 16) {
+ String s = "";
+ for (int j = i; j < Math.min(bytes.limit(), i+16); j++)
+ s = s + String.format(" %02x", bytes.get(j));
+
+ Log.i(TAG, s);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ gestureDetector = new GestureDetector(this, new GestureListener());
+
+ try {
+ long t0 = System.currentTimeMillis();
+ long t1 = System.currentTimeMillis();
+ timingOverhead = t1 - t0;
+
+ Bootstrap.setup(this);
+
+ Bootstrap.putenv("SAL_LOG=yes");
+
+ // Load a lot of shlibs here explicitly in advance because that
+ // makes debugging work better, sigh
+ Bootstrap.dlopen("libvcllo.so");
+ Bootstrap.dlopen("libmergedlo.so");
+ Bootstrap.dlopen("libswdlo.so");
+ Bootstrap.dlopen("libswlo.so");
+
+ // Log.i(TAG, "Sleeping NOW");
+ // Thread.sleep(20000);
+
+ context = com.sun.star.comp.helper.Bootstrap.defaultBootstrap_InitialComponentContext();
+
+ Log.i(TAG, "context is" + (context!=null ? " not" : "") + " null");
+
+ mcf = context.getServiceManager();
+
+ Log.i(TAG, "mcf is" + (mcf!=null ? " not" : "") + " null");
+
+ String input = getIntent().getStringExtra("input");
+ if (input == null)
+ input = "/assets/test1.odt";
+
+ // We need to fake up an argv, and the argv[0] even needs to
+ // point to some file name that we can pretend is the "program".
+ // setCommandArgs() will prefix argv[0] with the app's data
+ // directory.
+
+ String[] argv = { "lo-document-loader", input };
+
+ Bootstrap.setCommandArgs(argv);
+
+ Bootstrap.initVCL();
+
+ Object desktop = mcf.createInstanceWithContext
+ ("com.sun.star.frame.Desktop", context);
+
+ Log.i(TAG, "desktop is" + (desktop!=null ? " not" : "") + " null");
+
+ Bootstrap.initUCBHelper();
+
+ componentLoader = (XComponentLoader) UnoRuntime.queryInterface(XComponentLoader.class, desktop);
+
+ Log.i(TAG, "componentLoader is" + (componentLoader!=null ? " not" : "") + " null");
+
+ // Load the wanted document
+ new DocumentLoadTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, "file://" + input);
+
+ flipper = new ViewFlipper(this);
+
+ matchParent = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+
+ flipper.addView(new PageViewer(0), 0, matchParent);
+ for (int i = 0; i < PAGECACHE_PLUSMINUS; i++)
+ flipper.addView(new PageViewer(i+1), i+1, matchParent);
+ for (int i = 0; i < PAGECACHE_PLUSMINUS; i++)
+ flipper.addView(new PageViewer(-1), PAGECACHE_PLUSMINUS + i+1, matchParent);
+
+ setContentView(flipper);
+ }
+ catch (Exception e) {
+ e.printStackTrace(System.err);
+ finish();
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event)
+ {
+ return gestureDetector.onTouchEvent(event);
+ }
+}
+
+// vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/FileUtilities.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/FileUtilities.java
new file mode 100644
index 000000000000..2e21dbe5da61
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/FileUtilities.java
@@ -0,0 +1,159 @@
+package org.libreoffice.ui;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
+import java.util.Arrays;
+import java.util.Comparator;
+
+public class FileUtilities {
+
+ static final int ALL = -1;
+ static final int DOC = 0;
+ static final int CALC = 1;
+ static final int IMPRESS = 2;
+
+ static final int SORT_AZ = 0;
+ static final int SORT_ZA = 1;
+ /** Oldest Files First*/
+ static final int SORT_OLDEST = 2;
+ /** Newest Files First*/
+ static final int SORT_NEWEST = 3;
+ /** Largest Files First */
+ static final int SORT_LARGEST = 4;
+ /** Smallest Files First */
+ static final int SORT_SMALLEST = 5;
+
+ private static String[] fileExtensions = {".odt",".ods",".odp"};
+
+ static boolean isDoc(String filename){
+ if( filename.endsWith( fileExtensions[ DOC ] ) ){
+ return true;
+ }
+ return false;
+ }
+
+ static boolean isCalc(String filename){
+ if( filename.endsWith( fileExtensions[ CALC ] ) ){
+ return true;
+ }
+ return false;
+ }
+
+ static boolean isImpress(String filename){
+ if( filename.endsWith( fileExtensions[ IMPRESS ] ) ){
+ return true;
+ }
+ return false;
+ }
+
+ static FileFilter getFileFilter(int mode ){
+ if( mode != ALL){
+ final String ext = fileExtensions[ mode ];
+ return new FileFilter() {
+
+ public boolean accept(File pathname) {
+ if( pathname.getName().endsWith( ext ) ){
+ return true;
+ }
+ if( pathname.isDirectory() ){
+ return true;
+ }
+ return false;
+ }
+ };
+ }else{//return all
+ return new FileFilter() {
+
+ public boolean accept(File pathname) {
+ // TODO Auto-generated method stub
+ return true;
+ }
+ };
+ }
+ }
+
+ static FilenameFilter getFilenameFilter(int mode){
+ if( mode != ALL){
+ final String ext = fileExtensions[ mode ];
+ return new FilenameFilter() {
+
+ public boolean accept(File dir, String filename) {
+ if( filename.endsWith( ext ) ){
+ return true;
+ }
+ if( new File( dir , filename ).isDirectory() ){
+ return true;
+ }
+ return false;
+ }
+ };
+ }else{
+ return new FilenameFilter() {
+
+ public boolean accept(File dir, String filename) {
+ return true;
+ }
+ };
+ }
+ }
+
+ static void sortFiles(File[] files , int sortMode){
+ //Should really change all this to a switch statement...
+ if( sortMode == SORT_AZ ){
+ Arrays.sort( files , new Comparator<File>() {
+
+ public int compare(File lhs, File rhs) {
+ return lhs.getName().compareTo( rhs.getName() );
+ }
+ });
+ return;
+ }
+ if( sortMode == SORT_ZA ){
+ Arrays.sort( files , new Comparator<File>() {
+
+ public int compare(File lhs, File rhs) {
+ return rhs.getName().compareTo( lhs.getName() );
+ }
+ });
+ return;
+ }
+ if( sortMode == SORT_OLDEST ){
+ Arrays.sort( files , new Comparator<File>() {
+
+ public int compare(File lhs, File rhs) {
+ return Long.valueOf( lhs.lastModified() ).compareTo( rhs.lastModified() );
+ }
+ });
+ return;
+ }
+ if( sortMode == SORT_NEWEST ){
+ Arrays.sort( files , new Comparator<File>() {
+
+ public int compare(File lhs, File rhs) {
+ return Long.valueOf( rhs.lastModified() ).compareTo( lhs.lastModified() );
+ }
+ });
+ return;
+ }
+ if( sortMode == SORT_LARGEST ){
+ Arrays.sort( files , new Comparator<File>() {
+
+ public int compare(File lhs, File rhs) {
+ return Long.valueOf( rhs.length() ).compareTo( lhs.length() );
+ }
+ });
+ return;
+ }
+ if( sortMode == SORT_SMALLEST ){
+ Arrays.sort( files , new Comparator<File>() {
+
+ public int compare(File lhs, File rhs) {
+ return Long.valueOf( lhs.length() ).compareTo( rhs.length() );
+ }
+ });
+ return;
+ }
+ return;
+ }
+}
diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/GridItemAdapter.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/GridItemAdapter.java
new file mode 100644
index 000000000000..9585705d5e06
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/GridItemAdapter.java
@@ -0,0 +1,95 @@
+package org.libreoffice.ui;
+
+import java.io.File;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class GridItemAdapter extends BaseAdapter{
+ Context mContext;
+ File[] filePaths;
+ File currentDirectory;
+ String tag = "GridItemAdapter";
+
+ public GridItemAdapter(Context mContext, File[] filePaths) {
+ this.mContext = mContext;
+ this.filePaths = filePaths;
+ for(File fn : filePaths){
+ Log.d(tag, fn.getName());
+ }
+ }
+
+ public GridItemAdapter(Context mContext, File currentDirectory) {
+ this.mContext = mContext;
+ this.currentDirectory = currentDirectory;
+ filePaths = currentDirectory.listFiles();
+ }
+
+ public GridItemAdapter(Context mContext, File currentDirectory, File[] filteredFiles) {
+ this.mContext = mContext;
+ this.currentDirectory = currentDirectory;
+ filePaths = filteredFiles;
+ }
+
+ public int getCount() {
+ return filePaths.length;
+ }
+
+ public Object getItem(int position) {
+ return null;//filePaths[ position ];
+ }
+
+ public long getItemId(int arg0) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ View gridView;
+
+ if (convertView == null) {
+
+
+
+ } else {
+ gridView = (View) convertView;
+ }
+ gridView = new View(mContext);
+
+ // get layout from mobile.xml
+ gridView = inflater.inflate(R.layout.file_explorer_grid_item, null);
+
+ // set value into textview
+ TextView textView = (TextView) gridView
+ .findViewById(R.id.grid_item_label);
+ textView.setText(filePaths[position].getName());
+ // set image based on selected text
+ ImageView imageView = (ImageView) gridView
+ .findViewById(R.id.grid_item_image);
+ if( filePaths[position].getName().endsWith(".odt") ){
+ imageView.setImageResource(R.drawable.writer);
+ }
+ if( filePaths[position].getName().endsWith(".ods") ){
+ imageView.setImageResource(R.drawable.calc);
+ }
+ if( filePaths[position].getName().endsWith(".odp") ){
+ imageView.setImageResource(R.drawable.impress);
+ }
+ if( filePaths[position].isDirectory() ){//Is a folder
+ //Eventually have thumbnails of each sub file on a black circle
+ //For now just a folder icon
+ imageView.setImageResource(R.drawable.folder);
+ }
+ return gridView;
+ }
+
+}
diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/LibreOfficeUIActivity.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/LibreOfficeUIActivity.java
new file mode 100644
index 000000000000..d1e46a9b543d
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/LibreOfficeUIActivity.java
@@ -0,0 +1,548 @@
+package org.libreoffice.ui;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.prefs.Preferences;
+
+import android.app.ActionBar;
+import android.app.ActionBar.OnNavigationListener;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.DataSetObserver;
+import android.os.Bundle;
+import android.os.Environment;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+public class LibreOfficeUIActivity extends Activity implements OnNavigationListener {
+ private String tag = "file_manager";
+ private SharedPreferences prefs;
+ private File homeDirectory;
+ private File currentDirectory;
+ private int filterMode = FileUtilities.ALL;
+ private int viewMode;
+ private int sortMode;
+
+ FileFilter fileFilter;
+ FilenameFilter filenameFilter;
+ private String[] fileNames;
+ private File[] filePaths;
+
+
+ private static final String CURRENT_DIRECTORY_KEY = "CURRENT_DIRECTORY";
+ private static final String FILTER_MODE_KEY = "FILTER_MODE";
+ public static final String EXPLORER_VIEW_TYPE_KEY = "EXPLORER_VIEW_TYPE";
+ public static final String EXPLORER_PREFS_KEY = "EXPLORER_PREFS";
+ public static final String SORT_MODE_KEY = "SORT_MODE";
+
+ public static final int GRID_VIEW = 0;
+ public static final int LIST_VIEW = 1;
+
+ GridView gv;
+ ListView lv;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+ Log.d(tag, "onCreate");
+ //Set the "home" - top level - directory.
+ homeDirectory = new File(Environment.getExternalStorageDirectory(),"LibreOffice");
+ homeDirectory.mkdirs();
+ currentDirectory = homeDirectory;
+ //Load default settings
+
+
+ }
+
+ public void createUI(){
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayShowTitleEnabled(false);//This should show current directory if anything
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+ SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.file_view_modes,
+ android.R.layout.simple_spinner_dropdown_item);
+ actionBar.setListNavigationCallbacks(mSpinnerAdapter, this);
+ if( !currentDirectory.equals( homeDirectory )){
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ if( viewMode == GRID_VIEW){
+ // code to make a grid view
+ setContentView(R.layout.file_grid);
+ gv = (GridView)findViewById(R.id.file_explorer_grid_view);
+ fileNames = currentDirectory.list( FileUtilities.getFilenameFilter( filterMode ) );
+ filePaths = currentDirectory.listFiles( FileUtilities.getFileFilter( filterMode ) );
+ gv.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ File file = filePaths[position];
+ if(!file.isDirectory()){
+ open(fileNames[position]);
+ }else{
+ file = new File( currentDirectory, file.getName() );
+ openDirectory( file );
+ }
+
+ }
+ });
+ gv.setAdapter( new GridItemAdapter(getApplicationContext(), currentDirectory, filePaths ) );
+ actionBar.setSelectedNavigationItem( filterMode + 1 );//This triggers the listener which modifies the view.
+ }else{
+ setContentView(R.layout.file_list);
+ lv = (ListView)findViewById( R.id.file_explorer_list_view);
+ lv.setClickable(true);
+ fileNames = currentDirectory.list( FileUtilities.getFilenameFilter( filterMode ) );
+ filePaths = currentDirectory.listFiles( FileUtilities.getFileFilter( filterMode ) );
+ /*lv.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ Log.d(tag, "click!");
+ File file = filePaths[position];
+ if(!file.isDirectory()){
+ open(fileNames[position]);
+ }else{
+ file = new File( currentDirectory, file.getName() );
+ openDirectory( file );
+ }
+ }
+ });*/
+ lv.setAdapter( new ListItemAdapter(getApplicationContext(), filePaths) );
+ actionBar.setSelectedNavigationItem( filterMode + 1 );
+ }
+
+ }
+
+ public void openDirectory(File dir ){
+ currentDirectory = dir;
+ if( !currentDirectory.equals( homeDirectory )){
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }else{
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayHomeAsUpEnabled( false );
+ }
+ filePaths = currentDirectory.listFiles( FileUtilities.getFileFilter( filterMode ) );
+ fileNames = new String[ filePaths.length ];
+ FileUtilities.sortFiles( filePaths, sortMode );
+ for( int i = 0; i < fileNames.length; i++){
+ fileNames[ i ] = filePaths[ i ].getName();
+ }
+ if( viewMode == GRID_VIEW){
+ gv.setAdapter( new GridItemAdapter(getApplicationContext(), currentDirectory, filePaths ) );
+ }else{
+ lv.setAdapter( new ListItemAdapter(getApplicationContext(), filePaths) );
+ }
+ }
+
+ public void open(String file){
+ Intent i = new Intent( this , WriterViewerActivity.class );
+ i.putExtra( CURRENT_DIRECTORY_KEY , currentDirectory.getAbsolutePath() );
+ i.putExtra( FILTER_MODE_KEY , filterMode );
+ i.putExtra( EXPLORER_VIEW_TYPE_KEY , viewMode );
+ startActivity( i );
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.view_menu, menu);
+
+ MenuItem item = (MenuItem)menu.findItem(R.id.menu_view_toggle);
+ if( viewMode == GRID_VIEW){
+ item.setTitle(R.string.list_view);
+ item.setIcon( R.drawable.light_view_as_list );
+ }else{
+ item.setTitle(R.string.grid_view);
+ item.setIcon( R.drawable.light_view_as_grid );
+ }
+ return true;
+ }
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ if( !currentDirectory.equals( homeDirectory ) ){
+ openDirectory( currentDirectory.getParentFile() );
+ }
+ break;
+ case R.id.menu_view_toggle:
+ if( viewMode == GRID_VIEW){
+ viewMode = LIST_VIEW;
+ item.setTitle(R.string.grid_view);//Button points to next view.
+ item.setIcon( R.drawable.light_view_as_grid );
+
+ }else{
+ viewMode = GRID_VIEW;
+ item.setTitle(R.string.list_view);//Button points to next view.
+ item.setIcon( R.drawable.light_view_as_list );
+ }
+ createUI();
+ break;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ return true;
+ }
+
+ public void createDummyFileSystem(){
+ boolean mExternalStorageAvailable = false;
+ boolean mExternalStorageWriteable = false;
+ String state = Environment.getExternalStorageState();
+
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ mExternalStorageAvailable = mExternalStorageWriteable = true;
+ } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
+ mExternalStorageAvailable = true;
+ mExternalStorageWriteable = false;
+ } else {
+ mExternalStorageAvailable = mExternalStorageWriteable = false;
+ }
+ if( mExternalStorageAvailable && mExternalStorageWriteable ){//can also check if its writeable
+ Log.d(tag, Boolean.toString( currentDirectory.mkdir() ) );
+ try {
+ File[] removeList = currentDirectory.listFiles();
+ for(File item : removeList){
+ if(item.isDirectory())
+ continue;//Log.d(tag, item.getPath());
+ item.delete();
+ }
+ new File( currentDirectory , "d0.odp" ).createNewFile();
+ new File( currentDirectory , "d1.odt" ).createNewFile();
+ new File( currentDirectory , "d2.odt" ).createNewFile();
+ new File( currentDirectory , "d3.odp" ).createNewFile();
+ new File( currentDirectory , "d4.ods" ).createNewFile();
+ new File( currentDirectory , "d5.odt" ).createNewFile();
+ new File( currentDirectory , "d6.odp" ).createNewFile();
+ new File( currentDirectory , "d7.odt" ).createNewFile();
+ new File( currentDirectory , "d8.odt" ).createNewFile();
+ new File( currentDirectory , "d9.odp" ).createNewFile();
+ new File( currentDirectory , "d10.odp" ).createNewFile();
+ new File( currentDirectory , "d11.odt" ).createNewFile();
+ new File( currentDirectory , "d12.odt" ).createNewFile();
+ new File( currentDirectory , "d13.odp" ).createNewFile();
+ new File( currentDirectory , "d14.ods" ).createNewFile();
+ new File( currentDirectory , "d15.odt" ).createNewFile();
+ File templatesDirectory = new File( currentDirectory , "Templates" );
+ templatesDirectory.mkdir();
+ new File( templatesDirectory , "template1.odt" ).createNewFile();
+ new File( templatesDirectory , "template2.odt" ).createNewFile();
+ new File( templatesDirectory , "template3.ods" ).createNewFile();
+ new File( templatesDirectory , "template4.odp" ).createNewFile();
+ File regularDirectory = new File( currentDirectory , "Folder" );
+ regularDirectory.mkdir();
+ new File( regularDirectory , "yetAnotherDoc.odt" ).createNewFile();
+ new File( regularDirectory , "some really long file name.ods" ).createNewFile();
+ File anotherRegularDirectory = new File( regularDirectory , "AnotherFolder" );
+ anotherRegularDirectory.mkdir();
+ new File( anotherRegularDirectory , "yetAnotherDoc2.odt" ).createNewFile();
+ //Should put a folder in at some stage.
+
+ } catch (IOException e) {
+ Log.d(tag, "file io failure");
+ e.printStackTrace();
+ }
+ //Log.d(tag, fileStore.toString());
+ }
+ else{
+ Log.d(tag, "No External Storage");
+ }
+ }
+
+ @SuppressWarnings("unused")//see android:onClick properties in view_menu.xml
+ public void sortFiles(MenuItem item){
+ switch ( item.getItemId() ) {
+ case R.id.menu_sort_az:
+ if( sortMode == FileUtilities.SORT_AZ ){
+ sortMode = FileUtilities.SORT_ZA;
+ }else{
+ sortMode = FileUtilities.SORT_AZ;
+ }
+ break;
+ case R.id.menu_sort_modified:
+ if( sortMode == FileUtilities.SORT_NEWEST ){
+ sortMode = FileUtilities.SORT_OLDEST;
+ }else{
+ sortMode = FileUtilities.SORT_NEWEST;
+ }
+ break;
+ case R.id.menu_sort_size:
+ if( sortMode == FileUtilities.SORT_LARGEST ){
+ sortMode = FileUtilities.SORT_SMALLEST;
+ }else{
+ sortMode = FileUtilities.SORT_LARGEST;
+ }
+ break;
+ default:
+ break;
+ }
+ this.onResume();
+ return;
+ }
+
+ public void readPreferences(){
+ prefs = getSharedPreferences(EXPLORER_PREFS_KEY, MODE_PRIVATE);
+ viewMode = prefs.getInt( EXPLORER_VIEW_TYPE_KEY, GRID_VIEW);
+ sortMode = prefs.getInt( SORT_MODE_KEY, FileUtilities.SORT_AZ );
+ SharedPreferences defaultPrefs = PreferenceManager.getDefaultSharedPreferences( getBaseContext() );
+ filterMode = Integer.valueOf( defaultPrefs.getString( FILTER_MODE_KEY , "-1") );
+ sortMode = Integer.valueOf( defaultPrefs.getString( SORT_MODE_KEY , "-1") );
+ }
+
+ public void editPreferences(MenuItem item){
+ startActivity( new Intent( this , PreferenceEditor.class) );
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ // TODO Auto-generated method stub
+ super.onSaveInstanceState(outState);
+ outState.putString( CURRENT_DIRECTORY_KEY , currentDirectory.getAbsolutePath() );
+ outState.putInt( FILTER_MODE_KEY , filterMode );
+ outState.putInt( EXPLORER_VIEW_TYPE_KEY , viewMode );
+
+ Log.d(tag, currentDirectory.toString() + Integer.toString(filterMode ) + Integer.toString(viewMode) );
+ //prefs.edit().putInt(EXPLORER_VIEW_TYPE, viewType).commit();
+ Log.d(tag, "savedInstanceSate");
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ // TODO Auto-generated method stub
+ super.onRestoreInstanceState(savedInstanceState);
+ if( savedInstanceState.isEmpty() ){
+ return;
+ }
+ currentDirectory = new File( savedInstanceState.getString( CURRENT_DIRECTORY_KEY ) );
+ filterMode = savedInstanceState.getInt( FILTER_MODE_KEY , FileUtilities.ALL ) ;
+ viewMode = savedInstanceState.getInt( EXPLORER_VIEW_TYPE_KEY , GRID_VIEW );
+ //openDirectory( currentDirectory );
+ Log.d(tag, "onRestoreInstanceState");
+ Log.d(tag, currentDirectory.toString() + Integer.toString(filterMode ) + Integer.toString(viewMode) );
+ }
+
+ @Override
+ protected void onPause() {
+ //prefs.edit().putInt(EXPLORER_VIEW_TYPE, viewType).commit();
+ super.onPause();
+ Log.d(tag, "onPause");
+ }
+
+ @Override
+ protected void onResume() {
+ // TODO Auto-generated method stub
+ super.onResume();
+ Log.d(tag, "onResume");
+ readPreferences();// intent values take precedence over prefs?
+ Intent i = this.getIntent();
+ if( i.hasExtra( CURRENT_DIRECTORY_KEY ) ){
+ currentDirectory = new File( i.getStringExtra( CURRENT_DIRECTORY_KEY ) );
+ Log.d(tag, CURRENT_DIRECTORY_KEY);
+ }
+ if( i.hasExtra( FILTER_MODE_KEY ) ){
+ filterMode = i.getIntExtra( FILTER_MODE_KEY, FileUtilities.ALL);
+ Log.d(tag, FILTER_MODE_KEY);
+ }
+ if( i.hasExtra( EXPLORER_VIEW_TYPE_KEY ) ){
+ viewMode = i.getIntExtra( EXPLORER_VIEW_TYPE_KEY, GRID_VIEW);
+ Log.d(tag, EXPLORER_VIEW_TYPE_KEY);
+ }
+ createUI();
+ openDirectory( currentDirectory );
+ }
+
+ @Override
+ protected void onStart() {
+ // TODO Auto-generated method stub
+ super.onStart();
+ Log.d(tag, "onStart");
+ }
+
+ @Override
+ protected void onStop() {
+ // TODO Auto-generated method stub
+ super.onStop();
+ Log.d(tag, "onStop");
+ }
+
+ @Override
+ protected void onDestroy() {
+ // TODO Auto-generated method stub
+ super.onDestroy();
+
+ Log.d(tag, "onDestroy");
+ }
+
+ public boolean onNavigationItemSelected(int itemPosition, long itemId) {
+ filterMode = itemPosition -1; //bit of a hack, I know. -1 is ALL 0 Docs etc
+ openDirectory( currentDirectory );// Uses filter mode
+ return true;
+ }
+
+class ListItemAdapter implements ListAdapter{
+ private Context mContext;
+ private File[] filePaths;
+ private final long KB = 1024;
+ private final long MB = 1048576;
+
+ public ListItemAdapter(Context mContext, File[] filePaths) {
+ this.mContext = mContext;
+ this.filePaths = filePaths;
+ }
+
+ public int getCount() {
+ // TODO Auto-generated method stub
+ return filePaths.length;
+ }
+
+ public Object getItem(int arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public long getItemId(int arg0) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public int getItemViewType(int arg0) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ View listItem;
+
+ if (convertView == null) {
+ listItem = new View(mContext);
+ listItem = inflater.inflate(R.layout.file_list_item, null);
+ } else {
+ listItem = (View) convertView;
+ }
+ final int pos = position;
+ listItem.setClickable(true);
+ listItem.setOnClickListener(new OnClickListener() {
+
+ public void onClick(View v) {
+ Log.d("LIST", "click!");
+ if(filePaths[ pos ].isDirectory() ){
+ openDirectory( filePaths[ pos ] );
+ }else{
+ open( filePaths[ pos ].getName() );
+ }
+ }
+ });
+
+
+
+ // set value into textview
+ TextView filename = (TextView) listItem.findViewById(R.id.file_list_item_name);
+ filename.setText( filePaths[ position ].getName() );
+ //filename.setClickable(true);
+
+ TextView fileSize = (TextView) listItem.findViewById(R.id.file_list_item_size);
+ //TODO Give size in KB , MB as appropriate.
+ String size = "0B";
+ long length = filePaths[ position ].length();
+ if( length < KB ){
+ size = Long.toString( length ) + "B";
+ }
+ if( length >= KB && length < MB){
+ size = Long.toString( length/KB ) + "KB";
+ }
+ if( length >= MB){
+ size = Long.toString( length/MB ) + "MB";
+ }
+ fileSize.setText( size );
+ //fileSize.setClickable(true);
+
+ TextView fileDate = (TextView) listItem.findViewById(R.id.file_list_item_date);
+ SimpleDateFormat df = new SimpleDateFormat("dd MMM yyyy hh:ss");
+ Date date = new Date( filePaths[ position ].lastModified() );
+ //TODO format date
+ fileDate.setText( df.format( date ) );
+
+ // set image based on selected text
+ ImageView imageView = (ImageView) listItem.findViewById(R.id.file_list_item_icon);
+ if( filePaths[position].getName().endsWith(".odt") ){
+ imageView.setImageResource(R.drawable.writer);
+ }
+ if( filePaths[position].getName().endsWith(".ods") ){
+ imageView.setImageResource(R.drawable.calc);
+ }
+ if( filePaths[position].getName().endsWith(".odp") ){
+ imageView.setImageResource(R.drawable.impress);
+ }
+ if( filePaths[position].isDirectory() ){
+ //Eventually have thumbnails of each sub file on a black circle
+ //For now just a folder icon
+ imageView.setImageResource(R.drawable.folder);
+ }
+ //imageView.setClickable(true);
+ return listItem;
+ }
+
+ public int getViewTypeCount() {
+ // TODO Auto-generated method stub
+ return 1;
+ }
+
+ public boolean hasStableIds() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public boolean isEmpty() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public void registerDataSetObserver(DataSetObserver arg0) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void unregisterDataSetObserver(DataSetObserver arg0) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public boolean areAllItemsEnabled() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public boolean isEnabled(int position) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ }
+
+}
+
+
diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/ListItemAdapter.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/ListItemAdapter.java
new file mode 100644
index 000000000000..1e66e3a0501c
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/ListItemAdapter.java
@@ -0,0 +1,159 @@
+package org.libreoffice.ui;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+/*Currently this is class is not used but instead is implemented as an inner class in LibreOfficeUI.
+ * This is because I can't get the onItemClickListener to fire on the listview so I need to set an
+ * onClick listener in the adapter. ( I've tried turning off the focusability etc of the listitem
+ * contents but no dice...) */
+public class ListItemAdapter implements ListAdapter{
+ private Context mContext;
+ private File[] filePaths;
+ private final long KB = 1024;
+ private final long MB = 1048576;
+
+ public ListItemAdapter(Context mContext, File[] filePaths) {
+ this.mContext = mContext;
+ this.filePaths = filePaths;
+ }
+
+ public int getCount() {
+ // TODO Auto-generated method stub
+ return filePaths.length;
+ }
+
+ public Object getItem(int arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public long getItemId(int arg0) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public int getItemViewType(int arg0) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ View listItem;
+
+ if (convertView == null) {
+ listItem = new View(mContext);
+ listItem = inflater.inflate(R.layout.file_list_item, null);
+ } else {
+ listItem = (View) convertView;
+ }
+
+ listItem.setClickable(true);
+ listItem.setOnClickListener(new OnClickListener() {
+
+ public void onClick(View v) {
+ Log.d("LIST", "click!");
+ }
+ });
+
+
+
+ // set value into textview
+ TextView filename = (TextView) listItem.findViewById(R.id.file_list_item_name);
+ filename.setText( filePaths[ position ].getName() );
+ //filename.setClickable(true);
+
+ TextView fileSize = (TextView) listItem.findViewById(R.id.file_list_item_size);
+ //TODO Give size in KB , MB as appropriate.
+ String size = "0B";
+ long length = filePaths[ position ].length();
+ if( length < KB ){
+ size = Long.toString( length ) + "B";
+ }
+ if( length >= KB && length < MB){
+ size = Long.toString( length/KB ) + "KB";
+ }
+ if( length >= MB){
+ size = Long.toString( length/MB ) + "MB";
+ }
+ fileSize.setText( size );
+ //fileSize.setClickable(true);
+
+ TextView fileDate = (TextView) listItem.findViewById(R.id.file_list_item_date);
+ SimpleDateFormat df = new SimpleDateFormat("dd MMM yyyy hh:ss");
+ Date date = new Date( filePaths[ position ].lastModified() );
+ //TODO format date
+ fileDate.setText( df.format( date ) );
+
+ // set image based on selected text
+ ImageView imageView = (ImageView) listItem.findViewById(R.id.file_list_item_icon);
+ if( filePaths[position].getName().endsWith(".odt") ){
+ imageView.setImageResource(R.drawable.writer);
+ }
+ if( filePaths[position].getName().endsWith(".ods") ){
+ imageView.setImageResource(R.drawable.calc);
+ }
+ if( filePaths[position].getName().endsWith(".odp") ){
+ imageView.setImageResource(R.drawable.impress);
+ }
+ if( filePaths[position].isDirectory() ){
+ //Eventually have thumbnails of each sub file on a black circle
+ //For now just a folder icon
+ imageView.setImageResource(R.drawable.folder);
+ }
+ //imageView.setClickable(true);
+ return listItem;
+ }
+
+ public int getViewTypeCount() {
+ // TODO Auto-generated method stub
+ return 1;
+ }
+
+ public boolean hasStableIds() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public boolean isEmpty() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public void registerDataSetObserver(DataSetObserver arg0) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void unregisterDataSetObserver(DataSetObserver arg0) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public boolean areAllItemsEnabled() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public boolean isEnabled(int position) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+}
+
diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PageView.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PageView.java
new file mode 100644
index 000000000000..5edb6ea20926
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PageView.java
@@ -0,0 +1,63 @@
+package org.libreoffice.ui;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+public class PageView extends View{
+ private Bitmap bmp;
+ private Paint mPaintBlack;
+ private String tag = "PageView";
+
+ public PageView(Context context ) {
+ super(context);
+ bmp = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_page);
+ intialise();
+ }
+ public PageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ bmp = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_page);
+ Log.d( tag , bmp.toString());
+ intialise();
+ }
+ public PageView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ bmp = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_page);//load a "page"
+ intialise();
+ }
+
+ private void intialise(){
+ mPaintBlack = new Paint();
+ mPaintBlack.setARGB(255, 0, 0, 0);
+ Log.d(tag, " Doing some set-up");
+ }
+
+ public void setBitmap(Bitmap bmp){
+ this.bmp = bmp;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ Log.d(tag, "Draw");
+ Log.d(tag, Integer.toString(bmp.getHeight()));
+ if( bmp != null ){
+ int horizontalMargin = (int) (canvas.getWidth()*0.1);
+ //int verticalMargin = (int) (canvas.getHeight()*0.1);
+ int verticalMargin = horizontalMargin;
+ canvas.drawBitmap(bmp, new Rect(0, 0, bmp.getWidth(), bmp.getHeight()),
+ new Rect(horizontalMargin,verticalMargin,canvas.getWidth()-horizontalMargin,
+ canvas.getHeight()-verticalMargin),
+ mPaintBlack);//
+ }
+ if( bmp == null)
+ canvas.drawText("Bmp is null!", 100, 100, new Paint());
+ }
+
+}
diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PreferenceEditor.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PreferenceEditor.java
new file mode 100644
index 000000000000..dec509de1167
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PreferenceEditor.java
@@ -0,0 +1,18 @@
+package org.libreoffice.ui;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceActivity;
+
+public class PreferenceEditor extends PreferenceActivity {
+ public final static String FilterTypePrefKey = "FilterTypePreference";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource( R.xml.libreoffice_preferences );
+ //mPrefs = getSharedPreferences( LibreOfficeUIActivity.EXPLORER_PREFS_KEY , MODE_PRIVATE );
+ }
+}
diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/WriterViewerActivity.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/WriterViewerActivity.java
new file mode 100644
index 000000000000..fe2f1135b3a0
--- /dev/null
+++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/WriterViewerActivity.java
@@ -0,0 +1,37 @@
+package org.libreoffice.ui;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+public class WriterViewerActivity extends Activity{
+ private Bundle extras;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // TODO Auto-generated method stub
+ super.onCreate(savedInstanceState);
+ extras = getIntent().getExtras();
+ setContentView(R.layout.main);
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ // app icon in action bar clicked; go home
+ Intent intent = new Intent(this, LibreOfficeUIActivity.class);
+ intent.putExtras( extras );
+ //intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+}