diff options
author | Tomaž Vajngerl <tomaz.vajngerl@collabora.com> | 2014-06-16 16:23:39 +0200 |
---|---|---|
committer | Jan Holesovsky <kendy@collabora.com> | 2014-06-30 14:48:02 +0200 |
commit | ae14df5b316551455c4552444002429d2aac4715 (patch) | |
tree | 0b6c834d0c7219b7aac816018389ccb28cb84c72 /android/experimental | |
parent | 8339f8abc5470e5b9bad611e5577fccf34fef240 (diff) |
LOAndroid2: initial import of older Fennec version with LO changes
rebase on mozilla-central tag RELEASE_BASE_20120531
add LOEvent, LOThread, some LO icons
Change-Id: I0c3aba6d9715c60e32d8daa2002b4961eef476f0
Diffstat (limited to 'android/experimental')
99 files changed, 7700 insertions, 0 deletions
diff --git a/android/experimental/LOAndroid2/.gitignore b/android/experimental/LOAndroid2/.gitignore new file mode 100644 index 000000000000..d6bfc95b184b --- /dev/null +++ b/android/experimental/LOAndroid2/.gitignore @@ -0,0 +1,4 @@ +.gradle +/local.properties +/.idea/workspace.xml +.DS_Store diff --git a/android/experimental/LOAndroid2/.idea/.name b/android/experimental/LOAndroid2/.idea/.name new file mode 100644 index 000000000000..3300c569c980 --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/.name @@ -0,0 +1 @@ +LOAndroid
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/.idea/compiler.xml b/android/experimental/LOAndroid2/.idea/compiler.xml new file mode 100644 index 000000000000..217af471a9e6 --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/compiler.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="CompilerConfiguration"> + <option name="DEFAULT_COMPILER" value="Javac" /> + <resourceExtensions /> + <wildcardResourcePatterns> + <entry name="!?*.java" /> + <entry name="!?*.form" /> + <entry name="!?*.class" /> + <entry name="!?*.groovy" /> + <entry name="!?*.scala" /> + <entry name="!?*.flex" /> + <entry name="!?*.kt" /> + <entry name="!?*.clj" /> + </wildcardResourcePatterns> + <annotationProcessing> + <profile default="true" name="Default" enabled="false"> + <processorPath useClasspath="true" /> + </profile> + </annotationProcessing> + </component> +</project> + diff --git a/android/experimental/LOAndroid2/.idea/copyright/profiles_settings.xml b/android/experimental/LOAndroid2/.idea/copyright/profiles_settings.xml new file mode 100644 index 000000000000..e7bedf3377d4 --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ +<component name="CopyrightManager"> + <settings default="" /> +</component>
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/.idea/encodings.xml b/android/experimental/LOAndroid2/.idea/encodings.xml new file mode 100644 index 000000000000..e206d70d8595 --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/encodings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" /> +</project> + diff --git a/android/experimental/LOAndroid2/.idea/gradle.xml b/android/experimental/LOAndroid2/.idea/gradle.xml new file mode 100644 index 000000000000..736c7b5cffcc --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/gradle.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="GradleSettings"> + <option name="linkedExternalProjectsSettings"> + <GradleProjectSettings> + <option name="distributionType" value="DEFAULT_WRAPPED" /> + <option name="externalProjectPath" value="$PROJECT_DIR$" /> + <option name="modules"> + <set> + <option value="$PROJECT_DIR$" /> + <option value="$PROJECT_DIR$/app" /> + </set> + </option> + </GradleProjectSettings> + </option> + </component> +</project> + diff --git a/android/experimental/LOAndroid2/.idea/libraries/appcompat_v7_19_1_0.xml b/android/experimental/LOAndroid2/.idea/libraries/appcompat_v7_19_1_0.xml new file mode 100644 index 000000000000..7efbfdf278f4 --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/libraries/appcompat_v7_19_1_0.xml @@ -0,0 +1,10 @@ +<component name="libraryTable"> + <library name="appcompat-v7-19.1.0"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/19.1.0/classes.jar!/" /> + <root url="file://$PROJECT_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/19.1.0/res" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> +</component>
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/.idea/libraries/support_v4_19_1_0.xml b/android/experimental/LOAndroid2/.idea/libraries/support_v4_19_1_0.xml new file mode 100644 index 000000000000..1ca1ac68ad96 --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/libraries/support_v4_19_1_0.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="support-v4-19.1.0"> + <CLASSES> + <root url="jar://$USER_HOME$/Programs/android-sdk-linux/extras/android/m2repository/com/android/support/support-v4/19.1.0/support-v4-19.1.0.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$USER_HOME$/Programs/android-sdk-linux/extras/android/m2repository/com/android/support/support-v4/19.1.0/support-v4-19.1.0-sources.jar!/" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/.idea/misc.xml b/android/experimental/LOAndroid2/.idea/misc.xml new file mode 100644 index 000000000000..7f0862a388db --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/misc.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="EntryPointsManager"> + <entry_points version="2.0" /> + </component> + <component name="ProjectInspectionProfilesVisibleTreeState"> + <entry key="Project Default"> + <profile-state> + <expanded-state> + <State> + <id /> + </State> + </expanded-state> + <selected-state> + <State> + <id>Abstraction issues</id> + </State> + </selected-state> + </profile-state> + </entry> + </component> + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK"> + <output url="file://$PROJECT_DIR$/build/classes" /> + </component> + <component name="masterDetails"> + <states> + <state key="ScopeChooserConfigurable.UI"> + <settings> + <splitter-proportions> + <option name="proportions"> + <list> + <option value="0.2" /> + </list> + </option> + </splitter-proportions> + </settings> + </state> + </states> + </component> +</project> + diff --git a/android/experimental/LOAndroid2/.idea/modules.xml b/android/experimental/LOAndroid2/.idea/modules.xml new file mode 100644 index 000000000000..f08135d5d6bf --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/modules.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/LOAndroid.iml" filepath="$PROJECT_DIR$/LOAndroid.iml" /> + <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" /> + </modules> + </component> +</project> + diff --git a/android/experimental/LOAndroid2/.idea/scopes/scope_settings.xml b/android/experimental/LOAndroid2/.idea/scopes/scope_settings.xml new file mode 100644 index 000000000000..922003b8433b --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ +<component name="DependencyValidationManager"> + <state> + <option name="SKIP_IMPORT_STATEMENTS" value="false" /> + </state> +</component>
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/.idea/vcs.xml b/android/experimental/LOAndroid2/.idea/vcs.xml new file mode 100644 index 000000000000..def6a6a18457 --- /dev/null +++ b/android/experimental/LOAndroid2/.idea/vcs.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="" /> + </component> +</project> + diff --git a/android/experimental/LOAndroid2/LOAndroid.iml b/android/experimental/LOAndroid2/LOAndroid.iml new file mode 100644 index 000000000000..cd51bb402236 --- /dev/null +++ b/android/experimental/LOAndroid2/LOAndroid.iml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <excludeFolder url="file://$MODULE_DIR$/.gradle" /> + </content> + <orderEntry type="jdk" jdkName="1.7" jdkType="JavaSDK" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> + diff --git a/android/experimental/LOAndroid2/app/.gitignore b/android/experimental/LOAndroid2/app/.gitignore new file mode 100644 index 000000000000..796b96d1c402 --- /dev/null +++ b/android/experimental/LOAndroid2/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/android/experimental/LOAndroid2/app/app.iml b/android/experimental/LOAndroid2/app/app.iml new file mode 100644 index 000000000000..e89240b42923 --- /dev/null +++ b/android/experimental/LOAndroid2/app/app.iml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="LOAndroid" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> + <component name="FacetManager"> + <facet type="android-gradle" name="Android-Gradle"> + <configuration> + <option name="GRADLE_PROJECT_PATH" value=":app" /> + </configuration> + </facet> + <facet type="android" name="Android"> + <configuration> + <option name="SELECTED_BUILD_VARIANT" value="debug" /> + <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> + <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" /> + <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" /> + <option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" /> + <option name="ALLOW_USER_CONFIGURATION" value="false" /> + <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> + <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> + <option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" /> + <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" /> + </configuration> + </facet> + </component> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/debug" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates" /> + <excludeFolder url="file://$MODULE_DIR$/build/outputs" /> + </content> + <orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" exported="" name="appcompat-v7-19.1.0" level="project" /> + <orderEntry type="library" exported="" name="support-v4-19.1.0" level="project" /> + </component> +</module> + diff --git a/android/experimental/LOAndroid2/app/build.gradle b/android/experimental/LOAndroid2/app/build.gradle new file mode 100644 index 000000000000..7e98dd4c48c9 --- /dev/null +++ b/android/experimental/LOAndroid2/app/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'android' + +android { + compileSdkVersion 19 + buildToolsVersion "19.1.0" + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 19 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:19.+' +} diff --git a/android/experimental/LOAndroid2/app/proguard-rules.txt b/android/experimental/LOAndroid2/app/proguard-rules.txt new file mode 100644 index 000000000000..0b0be289afc1 --- /dev/null +++ b/android/experimental/LOAndroid2/app/proguard-rules.txt @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/quikee/Programs/android-sdk-linux/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#}
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/app/src/main/AndroidManifest.xml b/android/experimental/LOAndroid2/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..aef4bb7b591d --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.libreoffice" > + + <!-- App requires OpenGL ES 2.0 --> + <uses-feature android:glEsVersion="0x00020000" android:required="true" /> + + <application + android:allowBackup="true" + android:icon="@drawable/main" + android:label="@string/app_name" + android:hardwareAccelerated="true" + android:theme="@style/AppTheme" > + <activity + android:name="org.libreoffice.LibreOfficeMainActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOEvent.java b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOEvent.java new file mode 100644 index 000000000000..a5396693de89 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOEvent.java @@ -0,0 +1,67 @@ +package org.libreoffice; + +import android.graphics.Rect; +import android.util.Log; + +import org.mozilla.gecko.gfx.IntSize; +import org.mozilla.gecko.gfx.ViewportMetrics; + +public class LOEvent { + + public static final int SIZE_CHANGED = 1; + public static final int TILE_SIZE = 2; + public static final int VIEWPORT = 3; + public static final int DRAW = 4; + + private ViewportMetrics mViewportMetrics; + + public int mType; + private String mTypeString; + + ViewportMetrics viewportMetrics; + + public LOEvent(int type, int width, int height, int widthPixels, int heightPixels) { + mType = type; + mTypeString = "Size Changed"; + } + + public LOEvent(int type, IntSize tileSize) { + mType = type; + mTypeString = "Tile size"; + } + + public LOEvent(int type, ViewportMetrics viewportMetrics) { + mType = type; + mTypeString = "Viewport"; + mViewportMetrics = viewportMetrics; + } + + public LOEvent(int type, Rect rect) { + mType = type; + mTypeString = "Draw"; + } + + public static LOEvent draw(Rect rect) { + return new LOEvent(DRAW, rect); + } + + public static LOEvent sizeChanged(int width, int height, int widthPixels, int heightPixels) { + return new LOEvent(SIZE_CHANGED, width, height, widthPixels, heightPixels); + } + + public static LOEvent tileSize(IntSize tileSize) { + return new LOEvent(TILE_SIZE, tileSize); + } + + public static LOEvent viewport(ViewportMetrics viewportMetrics) { + return new LOEvent(VIEWPORT, viewportMetrics); + } + + public String getTypeString() { + return mTypeString; + } + + public ViewportMetrics getViewport() { + return mViewportMetrics; + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitShell.java b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitShell.java new file mode 100644 index 000000000000..69b634cc2231 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitShell.java @@ -0,0 +1,118 @@ +package org.libreoffice; + + +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient; +import org.mozilla.gecko.gfx.IntSize; +import org.mozilla.gecko.gfx.LayerController; +import org.mozilla.gecko.gfx.LayerView; + +import java.nio.ByteBuffer; + +public class LOKitShell { + private static final String LOGTAG = LOKitShell.class.getSimpleName(); + + public static int getDpi() { + return 96; + } + + public static int getScreenDepth() { + return 24; + } + + public static float computeRenderIntegrity() { + return 0.0f; + } + + public static ByteBuffer allocateDirectBuffer(int size) { + if (size <= 0) { + throw new IllegalArgumentException("Invalid size " + size); + } + + ByteBuffer directBuffer = ByteBuffer.allocateDirect(size); + //ByteBuffer directBuffer = nativeAllocateDirectBuffer(size); + if (directBuffer == null) { + throw new OutOfMemoryError("allocateDirectBuffer() returned null"); + } else if (!directBuffer.isDirect()) { + throw new AssertionError("allocateDirectBuffer() did not return a direct buffer"); + } + + return directBuffer; + } + + + public static void freeDirectBuffer(ByteBuffer buffer) { + if (buffer == null) { + return; + } + + if (!buffer.isDirect()) { + throw new IllegalArgumentException("buffer must be direct"); + } + //nativeFreeDirectBuffer(buffer); + return ; + } + + public static void bindWidgetTexture() { + } + + public static void sendEvent(LOEvent event) { + Log.i(LOGTAG, "Event: " + event.getTypeString()); + } + + public static void runGecko(String apkPath, String args, String url, boolean restoreSession) { + // run gecko -- it will spawn its own thread + // GeckoAppShell.nativeInit(); + + Log.i(LOGTAG, "post native init"); + + // Tell Gecko where the target byte buffer is for rendering + //GeckoAppShell.setSoftwareLayerClient(GeckoApp.mAppContext.getSoftwareLayerClient()); + + Log.i(LOGTAG, "setSoftwareLayerClient called"); + + // First argument is the .apk path + String combinedArgs = apkPath + " -greomni " + apkPath; + if (args != null) + combinedArgs += " " + args; + if (url != null) + combinedArgs += " -remote " + url; + if (restoreSession) + combinedArgs += " -restoresession"; + + DisplayMetrics metrics = new DisplayMetrics(); + LibreOfficeMainActivity.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics); + combinedArgs += " -width " + metrics.widthPixels + " -height " + metrics.heightPixels; + + LibreOfficeMainActivity.mAppContext.runOnUiThread(new Runnable() { + public void run() { + geckoLoaded(); + } + }); + + //LOKitShell.nativeRun(combinedArgs); + } + + // Called on the UI thread after Gecko loads. + private static void geckoLoaded() { + /*final LayerController layerController = LibreOfficeMainActivity.mAppContext.getLayerController(); + LayerView v = layerController.getView(); + mInputConnection = GeckoInputConnection.create(v); + v.setInputConnectionHandler(mInputConnection); + + layerController.setOnTouchListener(new View.OnTouchListener() { + public boolean onTouch(View view, MotionEvent event) { + if (event == null) + return true; + GeckoAppShell.sendEventToGecko(new GeckoEvent(event)); + return true; + } + }); + + layerController.notifyLayerClientOfGeometryChange();*/ + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java new file mode 100644 index 000000000000..37bb51c00926 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java @@ -0,0 +1,147 @@ +package org.libreoffice; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.JsonWriter; + +import org.mozilla.gecko.gfx.ViewportMetrics; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class LOKitThread extends Thread { + private static final String LOGTAG = "GeckoThread"; + + public ConcurrentLinkedQueue<LOEvent> gEvents = new ConcurrentLinkedQueue<LOEvent>(); + private ViewportMetrics mViewportMetrics; + private Random rand = new Random(); + + LOKitThread() { + } + + private void draw() throws InterruptedException { + final LibreOfficeMainActivity application = LibreOfficeMainActivity.mAppContext; + + Bitmap bitmap = application.getSoftwareLayerClient().getLayerController().getDrawable16("dummy_page"); + bitmap = convert(bitmap, Bitmap.Config.RGB_565); + + application.getSoftwareLayerClient().beginDrawing(bitmap.getWidth(), bitmap.getHeight()); + //application.getSoftwareLayerClient().beginDrawing(500,500); + + ByteBuffer buffer = application.getSoftwareLayerClient().getBuffer(); + bitmap.copyPixelsToBuffer(buffer.asIntBuffer()); + + /*short mainColor16 = convertTo16Bit(rand.nextInt()); + + short[] mainPattern = new short[500]; + Arrays.fill(mainPattern, mainColor16); + + buffer.rewind(); + ShortBuffer shortBuffer = buffer.asShortBuffer(); + for (int i = 0; i < 500; i++) { + shortBuffer.put(mainPattern); + }*/ + + StringWriter stringWriter = new StringWriter(); + + try { + JsonWriter writer = new JsonWriter(stringWriter); + writer.beginObject(); + if (mViewportMetrics == null) { + writer.name("x").value(0); + writer.name("y").value(0); + writer.name("width").value(bitmap.getWidth()); + writer.name("height").value(bitmap.getHeight()); + writer.name("pageWidth").value(1000); + writer.name("pageHeight").value(5000); + writer.name("offsetX").value(0); + writer.name("offsetY").value(0); + writer.name("zoom").value(1.0); + writer.name("allowZoom").value(true); + } else { + writer.name("x").value(mViewportMetrics.getOrigin().x); + writer.name("y").value(mViewportMetrics.getOrigin().y); + writer.name("width").value(mViewportMetrics.getSize().width); + writer.name("height").value(mViewportMetrics.getSize().height); + writer.name("pageWidth").value(mViewportMetrics.getPageSize().width); + writer.name("pageHeight").value(mViewportMetrics.getPageSize().height); + writer.name("offsetX").value(mViewportMetrics.getViewportOffset().x); + writer.name("offsetY").value(mViewportMetrics.getViewportOffset().y); + writer.name("zoom").value(mViewportMetrics.getZoomFactor()); + writer.name("allowZoom").value(mViewportMetrics.getAllowZoom()); + } + writer.name("backgroundColor").value("rgb(255,255,255)"); + writer.endObject(); + writer.close(); + } catch (IOException ex) { + } + + application.getSoftwareLayerClient().endDrawing(0, 0, bitmap.getWidth(), bitmap.getHeight(), stringWriter.toString(), false); + //application.getSoftwareLayerClient().endDrawing(0, 0, 500, 500, stringWriter.toString(), false); + application.runOnUiThread(new Runnable() { + @Override + public void run() { + application.getSoftwareLayerClient().handleMessage("Viewport:UpdateLater", null); + } + }); + + } + + private short convertTo16Bit(int color) { + int r = Color.red(color) >> 3, g = Color.green(color) >> 2, b = Color.blue(color) >> 3; + int c = ((r << 11) | (g << 5) | b); + // Swap endianness. + return (short) ((c >> 8) | ((c & 0xff) << 8)); + } + + private Bitmap convert(Bitmap bitmap, Bitmap.Config config) { + Bitmap convertedBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), config); + Canvas canvas = new Canvas(convertedBitmap); + Paint paint = new Paint(); + paint.setColor(Color.BLACK); + canvas.drawBitmap(bitmap, 0, 0, paint); + return convertedBitmap; + } + + + public void run() { + try { + boolean drawn = false; + while (true) { + + if (!gEvents.isEmpty()) { + processEvent(gEvents.poll()); + } else { + if(!drawn) { + draw(); + drawn = true; + } + Thread.sleep(100L); + } + } + } catch (InterruptedException ex) { + } + } + + private void processEvent(LOEvent event) throws InterruptedException { + switch (event.mType) { + case LOEvent.VIEWPORT: + mViewportMetrics = event.getViewport(); + break; + case LOEvent.DRAW: + draw(); + break; + case LOEvent.SIZE_CHANGED: + break; + } + } + + +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LibreOfficeMainActivity.java b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LibreOfficeMainActivity.java new file mode 100644 index 000000000000..a4fdf03c6503 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LibreOfficeMainActivity.java @@ -0,0 +1,94 @@ +package org.libreoffice; + +import android.app.Activity; +import android.os.Bundle; +import android.os.SystemClock; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient; +import org.mozilla.gecko.gfx.LayerController; +import org.mozilla.gecko.gfx.LayerView; + +public class LibreOfficeMainActivity extends Activity { + + private static final String LOGTAG = "LibreOfficeMainActivity"; + + private LinearLayout mMainLayout; + private RelativeLayout mGeckoLayout; + private static LayerController mLayerController; + private static GeckoSoftwareLayerClient mSoftwareLayerClient; + private static LOKitThread sLOKitThread; + + public static LibreOfficeMainActivity mAppContext; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + if (id == R.id.action_settings) { + return true; + } + return super.onOptionsItemSelected(item); + } + + public DisplayMetrics getDisplayMetrics() { + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + return metrics; + } + + /** + * Called when the activity is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + mAppContext = this; + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - onCreate"); + + setContentView(R.layout.activity_main); + + // setup gecko layout + mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout); + mMainLayout = (LinearLayout) findViewById(R.id.main_layout); + + + if (mLayerController == null) { + mLayerController = new LayerController(this); + mSoftwareLayerClient = new GeckoSoftwareLayerClient(this); + mLayerController.setLayerClient(mSoftwareLayerClient); + mGeckoLayout.addView(mLayerController.getView(), 0); + } + + mLayerController.notifyLayerClientOfGeometryChange(); + + sLOKitThread = new LOKitThread(); + sLOKitThread.start(); + + + Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - UI almost up"); + } + + public static GeckoSoftwareLayerClient getSoftwareLayerClient() { + return mSoftwareLayerClient; + } +}
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/GeckoEventListener.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/GeckoEventListener.java new file mode 100644 index 000000000000..670513f2cfb2 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/GeckoEventListener.java @@ -0,0 +1,44 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Sriram Ramasubramanian <sriram@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko; + +import org.json.JSONObject; + +public interface GeckoEventListener { + public void handleMessage(String event, JSONObject message); +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/BufferedCairoImage.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/BufferedCairoImage.java new file mode 100644 index 000000000000..b6c397767619 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/BufferedCairoImage.java @@ -0,0 +1,105 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import android.graphics.Bitmap; + +import org.libreoffice.LOKitShell; + +import java.nio.ByteBuffer; + +//import org.mozilla.gecko.GeckoAppShell; + +/** + * A Cairo image that simply saves a buffer of pixel data. + */ +public class BufferedCairoImage extends CairoImage { + private ByteBuffer mBuffer; + private IntSize mSize; + private int mFormat; + private boolean mNeedToFreeBuffer = false; + + /** + * Creates a buffered Cairo image from a byte buffer. + */ + public BufferedCairoImage(ByteBuffer inBuffer, int inWidth, int inHeight, int inFormat) { + mBuffer = inBuffer; + mSize = new IntSize(inWidth, inHeight); + mFormat = inFormat; + } + + /** + * Creates a buffered Cairo image from an Android bitmap. + */ + public BufferedCairoImage(Bitmap bitmap) { + mFormat = CairoUtils.bitmapConfigToCairoFormat(bitmap.getConfig()); + mSize = new IntSize(bitmap.getWidth(), bitmap.getHeight()); + mNeedToFreeBuffer = true; + // XXX Why is this * 4? Shouldn't it depend on mFormat? + mBuffer = /*GeckoAppShell*/LOKitShell.allocateDirectBuffer(mSize.getArea() * 4); + + bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer()); + } + + protected void finalize() throws Throwable { + try { + if (mNeedToFreeBuffer && mBuffer != null) + /*GeckoAppShell*/ LOKitShell.freeDirectBuffer(mBuffer); + mNeedToFreeBuffer = false; + mBuffer = null; + } finally { + super.finalize(); + } + } + + @Override + public ByteBuffer getBuffer() { + return mBuffer; + } + + @Override + public IntSize getSize() { + return mSize; + } + + @Override + public int getFormat() { + return mFormat; + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoGLInfo.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoGLInfo.java new file mode 100644 index 000000000000..16417e17b51f --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoGLInfo.java @@ -0,0 +1,67 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import javax.microedition.khronos.opengles.GL10; + +/** Information needed to render Cairo bitmaps using OpenGL ES. */ +public class CairoGLInfo { + public final int internalFormat; + public final int format; + public final int type; + + public CairoGLInfo(int cairoFormat) { + switch (cairoFormat) { + case CairoImage.FORMAT_ARGB32: + internalFormat = format = GL10.GL_RGBA; type = GL10.GL_UNSIGNED_BYTE; + break; + case CairoImage.FORMAT_RGB24: + internalFormat = format = GL10.GL_RGB; type = GL10.GL_UNSIGNED_BYTE; + break; + case CairoImage.FORMAT_RGB16_565: + internalFormat = format = GL10.GL_RGB; type = GL10.GL_UNSIGNED_SHORT_5_6_5; + break; + case CairoImage.FORMAT_A8: + case CairoImage.FORMAT_A1: + throw new RuntimeException("Cairo FORMAT_A1 and FORMAT_A8 unsupported"); + default: + throw new RuntimeException("Unknown Cairo format"); + } + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoImage.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoImage.java new file mode 100644 index 000000000000..06c389dd0524 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoImage.java @@ -0,0 +1,58 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import java.nio.ByteBuffer; + +/* + * A bitmap with pixel data in one of the formats that Cairo understands. + */ +public abstract class CairoImage { + public abstract ByteBuffer getBuffer(); + + public abstract IntSize getSize(); + public abstract int getFormat(); + + public static final int FORMAT_INVALID = -1; + public static final int FORMAT_ARGB32 = 0; + public static final int FORMAT_RGB24 = 1; + public static final int FORMAT_A8 = 2; + public static final int FORMAT_A1 = 3; + public static final int FORMAT_RGB16_565 = 4; +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoUtils.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoUtils.java new file mode 100644 index 000000000000..00bd896e1664 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoUtils.java @@ -0,0 +1,85 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.gfx.CairoImage; +import android.graphics.Bitmap; +import javax.microedition.khronos.opengles.GL10; + +/** + * Utility methods useful when displaying Cairo bitmaps using OpenGL ES. + */ +public class CairoUtils { + private CairoUtils() { /* Don't call me. */ } + + public static int bitsPerPixelForCairoFormat(int cairoFormat) { + switch (cairoFormat) { + case CairoImage.FORMAT_A1: return 1; + case CairoImage.FORMAT_A8: return 8; + case CairoImage.FORMAT_RGB16_565: return 16; + case CairoImage.FORMAT_RGB24: return 24; + case CairoImage.FORMAT_ARGB32: return 32; + default: + throw new RuntimeException("Unknown Cairo format"); + } + } + + public static int bitmapConfigToCairoFormat(Bitmap.Config config) { + if (config == null) + return CairoImage.FORMAT_ARGB32; /* Droid Pro fix. */ + + switch (config) { + case ALPHA_8: return CairoImage.FORMAT_A8; + case ARGB_4444: throw new RuntimeException("ARGB_444 unsupported"); + case ARGB_8888: return CairoImage.FORMAT_ARGB32; + case RGB_565: return CairoImage.FORMAT_RGB16_565; + default: throw new RuntimeException("Unknown Skia bitmap config"); + } + } + + public static Bitmap.Config cairoFormatTobitmapConfig(int format) { + switch (format) { + case CairoImage.FORMAT_A8: return Bitmap.Config.ALPHA_8; + case CairoImage.FORMAT_ARGB32: return Bitmap.Config.ARGB_8888; + case CairoImage.FORMAT_RGB16_565: return Bitmap.Config.RGB_565; + default: + throw new RuntimeException("Unknown CairoImage format"); + } + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CheckerboardImage.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CheckerboardImage.java new file mode 100644 index 000000000000..f9ff9664bc62 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CheckerboardImage.java @@ -0,0 +1,170 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.libreoffice.LOKitShell; +//import org.mozilla.gecko.GeckoAppShell; +import android.graphics.Color; +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; +import java.util.Arrays; + +/** A Cairo image that displays a tinted checkerboard. */ +public class CheckerboardImage extends CairoImage { + // The width and height of the checkerboard tile. + private static final int SIZE = 16; + // The pixel format of the checkerboard tile. + private static final int FORMAT = CairoImage.FORMAT_RGB16_565; + // The color to mix in to tint the background color. + private static final int TINT_COLOR = Color.GRAY; + // The amount to mix in. + private static final float TINT_OPACITY = 0.4f; + + private ByteBuffer mBuffer; + private int mMainColor; + private boolean mShowChecks; + + /** Creates a new checkerboard image. */ + public CheckerboardImage() { + int bpp = CairoUtils.bitsPerPixelForCairoFormat(FORMAT); + mBuffer = /*GeckoAppShell*/LOKitShell.allocateDirectBuffer(SIZE * SIZE * bpp / 8); + update(true, Color.WHITE); + } + + /** Returns the current color of the checkerboard. */ + public int getColor() { + return mMainColor; + } + + /** Returns whether or not we are currently showing checks on the checkerboard. */ + public boolean getShowChecks() { + return mShowChecks; + } + + /** Updates the checkerboard image. If showChecks is true, then create a + checkerboard image that is tinted to the color. Otherwise just return a flat + image of the color. */ + public void update(boolean showChecks, int color) { + mMainColor = color; + mShowChecks = showChecks; + + short mainColor16 = convertTo16Bit(mMainColor); + + mBuffer.rewind(); + ShortBuffer shortBuffer = mBuffer.asShortBuffer(); + + if (!mShowChecks) { + short color16 = convertTo16Bit(mMainColor); + short[] fillBuffer = new short[SIZE]; + Arrays.fill(fillBuffer, color16); + + for (int i = 0; i < SIZE; i++) { + shortBuffer.put(fillBuffer); + } + + return; + } + + short tintColor16 = convertTo16Bit(tint(mMainColor)); + + short[] mainPattern = new short[SIZE / 2], tintPattern = new short[SIZE / 2]; + Arrays.fill(mainPattern, mainColor16); + Arrays.fill(tintPattern, tintColor16); + + // The checkerboard pattern looks like this: + // + // +---+---+ + // | N | T | N = normal + // +---+---+ T = tinted + // | T | N | + // +---+---+ + + for (int i = 0; i < SIZE / 2; i++) { + shortBuffer.put(mainPattern); + shortBuffer.put(tintPattern); + } + for (int i = SIZE / 2; i < SIZE; i++) { + shortBuffer.put(tintPattern); + shortBuffer.put(mainPattern); + } + } + + // Tints the given color appropriately and returns the tinted color. + private int tint(int color) { + float negTintOpacity = 1.0f - TINT_OPACITY; + float r = Color.red(color) * negTintOpacity + Color.red(TINT_COLOR) * TINT_OPACITY; + float g = Color.green(color) * negTintOpacity + Color.green(TINT_COLOR) * TINT_OPACITY; + float b = Color.blue(color) * negTintOpacity + Color.blue(TINT_COLOR) * TINT_OPACITY; + return Color.rgb(Math.round(r), Math.round(g), Math.round(b)); + } + + // Converts a 32-bit ARGB color to 16-bit R5G6B5, truncating values and discarding the alpha + // channel. + private short convertTo16Bit(int color) { + int r = Color.red(color) >> 3, g = Color.green(color) >> 2, b = Color.blue(color) >> 3; + int c = ((r << 11) | (g << 5) | b); + // Swap endianness. + return (short)((c >> 8) | ((c & 0xff) << 8)); + } + + @Override + protected void finalize() throws Throwable { + try { + if (mBuffer != null) { + /*GeckoAppShell*/LOKitShell.freeDirectBuffer(mBuffer); + } + } finally { + super.finalize(); + } + } + + @Override + public ByteBuffer getBuffer() { + return mBuffer; + } + + @Override + public IntSize getSize() { + return new IntSize(SIZE, SIZE); + } + + @Override + public int getFormat() { + return FORMAT; + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FloatSize.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FloatSize.java new file mode 100644 index 000000000000..21a712ca7318 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FloatSize.java @@ -0,0 +1,88 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * Chris Lord <chrislord.net@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.util.FloatUtils; +import org.json.JSONException; +import org.json.JSONObject; + +public class FloatSize { + public final float width, height; + + public FloatSize(FloatSize size) { width = size.width; height = size.height; } + public FloatSize(IntSize size) { width = size.width; height = size.height; } + public FloatSize(float aWidth, float aHeight) { width = aWidth; height = aHeight; } + + public FloatSize(JSONObject json) { + try { + width = (float)json.getDouble("width"); + height = (float)json.getDouble("height"); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return "(" + width + "," + height + ")"; + } + + public boolean isPositive() { + return (width > 0 && height > 0); + } + + public boolean fuzzyEquals(FloatSize size) { + return (FloatUtils.fuzzyEquals(size.width, width) && + FloatUtils.fuzzyEquals(size.height, height)); + } + + public FloatSize scale(float factor) { + return new FloatSize(width * factor, height * factor); + } + + /* + * Returns the size that represents a linear transition between this size and `to` at time `t`, + * which is on the scale [0, 1). + */ + public FloatSize interpolate(FloatSize to, float t) { + return new FloatSize(FloatUtils.interpolate(width, to.width, t), + FloatUtils.interpolate(height, to.height, t)); + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java new file mode 100644 index 000000000000..9385d8185bf0 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java @@ -0,0 +1,505 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * Chris Lord <chrislord.net@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PointF; +import android.graphics.Rect; +import android.util.DisplayMetrics; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; +import org.libreoffice.LOEvent; +import org.libreoffice.LOKitShell; +import org.libreoffice.LibreOfficeMainActivity; +import org.mozilla.gecko.GeckoEventListener; +import org.mozilla.gecko.util.FloatUtils; + +import java.nio.ByteBuffer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +//import org.mozilla.gecko.GeckoApp; +//import org.mozilla.gecko.GeckoAppShell; +//import org.mozilla.gecko.GeckoEvent; + +/** + * Transfers a software-rendered Gecko to an ImageLayer so that it can be rendered by our + * compositor. + * <p/> + * TODO: Throttle down Gecko's priority when we pan and zoom. + */ +public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventListener { + private static final String LOGTAG = "GeckoSoftwareLayerClient"; + private static final IntSize TILE_SIZE = new IntSize(256, 256); + private static final long MIN_VIEWPORT_CHANGE_DELAY = 350L; + private static Pattern sColorPattern; + private Context mContext; + private int mFormat; + private IntSize mScreenSize; + private IntSize mBufferSize; + private ByteBuffer mBuffer; + private Layer mTileLayer; + /* The viewport rect that Gecko is currently displaying. */ + private ViewportMetrics mGeckoViewport; + private CairoImage mCairoImage; + private long mLastViewportChangeTime; + private boolean mPendingViewportAdjust; + private boolean mViewportSizeChanged; + // Whether or not the last paint we got used direct texturing + private boolean mHasDirectTexture; + // mUpdateViewportOnEndDraw is used to indicate that we received a + // viewport update notification while drawing. therefore, when the + // draw finishes, we need to update the entire viewport rather than + // just the page size. this boolean should always be accessed from + // inside a transaction, so no synchronization is needed. + private boolean mUpdateViewportOnEndDraw; + + public GeckoSoftwareLayerClient(Context context) { + mContext = context; + + mScreenSize = new IntSize(0, 0); + mBufferSize = new IntSize(0, 0); + mFormat = CairoImage.FORMAT_RGB16_565; + + mCairoImage = new CairoImage() { + @Override + public ByteBuffer getBuffer() { + return mBuffer; + } + + @Override + public IntSize getSize() { + return mBufferSize; + } + + @Override + public int getFormat() { + return mFormat; + } + }; + + mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE); + } + + // Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color + // cannot be parsed, returns white. + private static int parseColorFromGecko(String string) { + if (sColorPattern == null) { + sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)"); + } + + Matcher matcher = sColorPattern.matcher(string); + if (!matcher.matches()) { + return Color.WHITE; + } + + int r = Integer.parseInt(matcher.group(1)); + int g = Integer.parseInt(matcher.group(2)); + int b = Integer.parseInt(matcher.group(3)); + return Color.rgb(r, g, b); + } + + public int getWidth() { + return mBufferSize.width; + } + + public int getHeight() { + return mBufferSize.height; + } + + protected void finalize() throws Throwable { + try { + if (mBuffer != null) + /*GeckoAppShell*/ LOKitShell.freeDirectBuffer(mBuffer); + mBuffer = null; + } finally { + super.finalize(); + } + } + + /** + * Attaches the root layer to the layer controller so that Gecko appears. + */ + @Override + public void setLayerController(LayerController layerController) { + super.setLayerController(layerController); + + layerController.setRoot(mTileLayer); + if (mGeckoViewport != null) { + layerController.setViewportMetrics(mGeckoViewport); + } + + //GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this); + //GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this); + + // This needs to happen before a call to sendResizeEventIfNecessary + // happens, but only needs to be called once. As that is only called by + // the layer controller or this, here is a safe place to do so. + if (mTileLayer instanceof MultiTileLayer) { + //GeckoEvent event = new GeckoEvent(GeckoEvent.TILE_SIZE, TILE_SIZE); + //GeckoAppShell.sendEventToGecko(event); + LOKitShell.sendEvent(LOEvent.tileSize(TILE_SIZE)); + } + + sendResizeEventIfNecessary(); + } + + private void setHasDirectTexture(boolean hasDirectTexture) { + if (hasDirectTexture == mHasDirectTexture) + return; + + mHasDirectTexture = hasDirectTexture; + + IntSize tileSize; + if (mHasDirectTexture) { + mTileLayer = new WidgetTileLayer(mCairoImage); + tileSize = new IntSize(0, 0); + } else { + mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE); + tileSize = TILE_SIZE; + } + + getLayerController().setRoot(mTileLayer); + + //GeckoEvent event = new GeckoEvent(GeckoEvent.TILE_SIZE, tileSize); + //GeckoAppShell.sendEventToGecko(event); + LOKitShell.sendEvent(LOEvent.tileSize(tileSize)); + + // Force a resize event to be sent because the results of this + // are different depending on what tile system we're using + sendResizeEventIfNecessary(true); + } + + public void beginDrawing(int width, int height) { + beginTransaction(mTileLayer); + + if (mBufferSize.width != width || mBufferSize.height != height) { + mBufferSize = new IntSize(width, height); + + // Reallocate the buffer if necessary + + // * 2 because it's a 16-bit buffer (so 2 bytes per pixel). + int size = mBufferSize.getArea() * 2; + if (mBuffer == null || mBuffer.capacity() != size) { + // Free the old buffer + if (mBuffer != null) { + /*GeckoAppShell*/ + LOKitShell.freeDirectBuffer(mBuffer); + mBuffer = null; + } + + mBuffer = /*GeckoAppShell*/LOKitShell.allocateDirectBuffer(size); + } + } + } + + private void updateViewport(String viewportDescription, final boolean onlyUpdatePageSize) { + try { + JSONObject viewportObject = new JSONObject(viewportDescription); + + // save and restore the viewport size stored in java; never let the + // JS-side viewport dimensions override the java-side ones because + // java is the One True Source of this information, and allowing JS + // to override can lead to race conditions where this data gets clobbered. + FloatSize viewportSize = getLayerController().getViewportSize(); + mGeckoViewport = new ViewportMetrics(viewportObject); + mGeckoViewport.setSize(viewportSize); + + LayerController controller = getLayerController(); + PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin(); + mTileLayer.setOrigin(PointUtils.round(displayportOrigin)); + mTileLayer.setResolution(mGeckoViewport.getZoomFactor()); + + if (onlyUpdatePageSize) { + // Don't adjust page size when zooming unless zoom levels are + // approximately equal. + if (FloatUtils.fuzzyEquals(controller.getZoomFactor(), + mGeckoViewport.getZoomFactor())) + controller.setPageSize(mGeckoViewport.getPageSize()); + } else { + Log.d(LOGTAG, "Received viewport update from gecko"); + controller.setViewportMetrics(mGeckoViewport); + controller.abortPanZoomAnimation(); + } + + // Update the background color, if it's present. + String backgroundColorString = viewportObject.optString("backgroundColor"); + if (backgroundColorString != null) { + controller.setCheckerboardColor(parseColorFromGecko(backgroundColorString)); + } + } catch (JSONException e) { + Log.e(LOGTAG, "Bad viewport description: " + viewportDescription); + throw new RuntimeException(e); + } + } + + public ByteBuffer getBuffer() { + return mBuffer; + } + + /* + * TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require + * a little more JNI magic. + */ + public void endDrawing(int x, int y, int width, int height, String metadata, boolean hasDirectTexture) { + synchronized (getLayerController()) { + try { + updateViewport(metadata, !mUpdateViewportOnEndDraw); + mUpdateViewportOnEndDraw = false; + Rect rect = new Rect(x, y, x + width, y + height); + + setHasDirectTexture(hasDirectTexture); + + if (!mHasDirectTexture) + ((MultiTileLayer) mTileLayer).invalidate(rect); + } finally { + endTransaction(mTileLayer); + } + } + } + + public ViewportMetrics getGeckoViewportMetrics() { + // Return a copy, as we modify this inside the Gecko thread + if (mGeckoViewport != null) + return new ViewportMetrics(mGeckoViewport); + return null; + } + + public void copyPixelsFromMultiTileLayer(Bitmap target) { + Canvas canvas = new Canvas(target); + ByteBuffer tileBuffer = mBuffer.slice(); + int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8; + + for (int y = 0; y < mBufferSize.height; y += TILE_SIZE.height) { + for (int x = 0; x < mBufferSize.width; x += TILE_SIZE.width) { + // Calculate tile size + IntSize tileSize = new IntSize(Math.min(mBufferSize.width - x, TILE_SIZE.width), + Math.min(mBufferSize.height - y, TILE_SIZE.height)); + + // Create a Bitmap from this tile + Bitmap tile = Bitmap.createBitmap(tileSize.width, tileSize.height, + CairoUtils.cairoFormatTobitmapConfig(mFormat)); + tile.copyPixelsFromBuffer(tileBuffer.asIntBuffer()); + + // Copy the tile to the master Bitmap and recycle it + canvas.drawBitmap(tile, x, y, null); + tile.recycle(); + + // Progress the buffer to the next tile + tileBuffer.position(tileSize.getArea() * bpp); + tileBuffer = tileBuffer.slice(); + } + } + } + + public Bitmap getBitmap() { + // Begin a tile transaction, otherwise the buffer can be destroyed while + // we're reading from it. + beginTransaction(mTileLayer); + try { + if (mBuffer == null || mBufferSize.width <= 0 || mBufferSize.height <= 0) + return null; + try { + Bitmap bitmap = null; + + if (mTileLayer instanceof MultiTileLayer) { + bitmap = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height, + CairoUtils.cairoFormatTobitmapConfig(mFormat)); + copyPixelsFromMultiTileLayer(bitmap); + } else if (mTileLayer instanceof SingleTileLayer) { + bitmap = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height, + CairoUtils.cairoFormatTobitmapConfig(mFormat)); + bitmap.copyPixelsFromBuffer(mBuffer.asIntBuffer()); + } else { + Log.w(LOGTAG, "getBitmap() called on a layer (" + mTileLayer + ") we don't know how to get a bitmap from"); + } + + return bitmap; + } catch (OutOfMemoryError oom) { + Log.w(LOGTAG, "Unable to create bitmap", oom); + return null; + } + } finally { + endTransaction(mTileLayer); + } + } + + /** + * Returns the back buffer. This function is for Gecko to use. + */ + public ByteBuffer lockBuffer() { + return mBuffer; + } + + /** + * Gecko calls this function to signal that it is done with the back buffer. After this call, + * it is forbidden for Gecko to touch the buffer. + */ + public void unlockBuffer() { + /* no-op */ + } + + @Override + public void geometryChanged() { + /* Let Gecko know if the screensize has changed */ + sendResizeEventIfNecessary(); + render(); + } + + private void sendResizeEventIfNecessary() { + sendResizeEventIfNecessary(false); + } + + /* Informs Gecko that the screen size has changed. */ + private void sendResizeEventIfNecessary(boolean force) { + DisplayMetrics metrics = new DisplayMetrics(); + //GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics); + LibreOfficeMainActivity.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics); + + // Return immediately if the screen size hasn't changed or the viewport + // size is zero (which indicates that the rendering surface hasn't been + // allocated yet). + boolean screenSizeChanged = (metrics.widthPixels != mScreenSize.width || + metrics.heightPixels != mScreenSize.height); + boolean viewportSizeValid = (getLayerController() != null && + getLayerController().getViewportSize().isPositive()); + if (!(force || (screenSizeChanged && viewportSizeValid))) { + return; + } + + mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels); + IntSize bufferSize; + + // Round up depending on layer implementation to remove texture wastage + if (mTileLayer instanceof MultiTileLayer) { + // Round to the next multiple of the tile size, respecting maximum texture size + bufferSize = new IntSize(((mScreenSize.width + LayerController.MIN_BUFFER.width - 1) / TILE_SIZE.width + 1) * TILE_SIZE.width, + ((mScreenSize.height + LayerController.MIN_BUFFER.height - 1) / TILE_SIZE.height + 1) * TILE_SIZE.height); + + } else { + int maxSize = getLayerController().getView().getMaxTextureSize(); + + // XXX Integrate gralloc/tiling work to circumvent this + if (mScreenSize.width > maxSize || mScreenSize.height > maxSize) + throw new RuntimeException("Screen size of " + mScreenSize + " larger than maximum texture size of " + maxSize); + + // Round to next power of two until we have NPOT texture support + bufferSize = new IntSize(Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.width + LayerController.MIN_BUFFER.width)), + Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.height + LayerController.MIN_BUFFER.height))); + } + + Log.i(LOGTAG, "Screen-size changed to " + mScreenSize); + + /*GeckoEvent event = new GeckoEvent(GeckoEvent.SIZE_CHANGED, + bufferSize.width, bufferSize.height, + metrics.widthPixels, metrics.heightPixels); + GeckoAppShell.sendEventToGecko(event);*/ + LOKitShell.sendEvent(LOEvent.sizeChanged(bufferSize.width, bufferSize.height, metrics.widthPixels, metrics.heightPixels)); + } + + @Override + public void viewportSizeChanged() { + mViewportSizeChanged = true; + } + + @Override + public void render() { + adjustViewportWithThrottling(); + } + + private void adjustViewportWithThrottling() { + if (!getLayerController().getRedrawHint()) + return; + + if (mPendingViewportAdjust) + return; + + long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime; + if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) { + getLayerController().getView().postDelayed( + new Runnable() { + public void run() { + mPendingViewportAdjust = false; + adjustViewport(); + } + }, MIN_VIEWPORT_CHANGE_DELAY - timeDelta + ); + mPendingViewportAdjust = true; + return; + } + + adjustViewport(); + } + + private void adjustViewport() { + ViewportMetrics viewportMetrics = new ViewportMetrics(getLayerController().getViewportMetrics()); + + PointF viewportOffset = viewportMetrics.getOptimumViewportOffset(mBufferSize); + viewportMetrics.setViewportOffset(viewportOffset); + viewportMetrics.setViewport(viewportMetrics.getClampedViewport()); + + //GeckoAppShell.sendEventToGecko(new GeckoEvent(viewportMetrics)); + LOKitShell.sendEvent(LOEvent.viewport(viewportMetrics)); + if (mViewportSizeChanged) { + mViewportSizeChanged = false; + //GeckoAppShell.viewSizeChanged(); + } + + mLastViewportChangeTime = System.currentTimeMillis(); + } + + public void handleMessage(String event, JSONObject message) { + if ("Viewport:UpdateAndDraw".equals(event)) { + mUpdateViewportOnEndDraw = true; + + // Redraw everything. + Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height); + //GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.DRAW, rect)); + LOKitShell.sendEvent(LOEvent.draw(rect)); + } else if ("Viewport:UpdateLater".equals(event)) { + mUpdateViewportOnEndDraw = true; + } + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/InputConnectionHandler.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/InputConnectionHandler.java new file mode 100644 index 000000000000..6fef53fb0535 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/InputConnectionHandler.java @@ -0,0 +1,15 @@ +package org.mozilla.gecko.gfx; + +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.KeyEvent; + +public interface InputConnectionHandler +{ + InputConnection onCreateInputConnection(EditorInfo outAttrs); + boolean onKeyPreIme(int keyCode, KeyEvent event); + boolean onKeyDown(int keyCode, KeyEvent event); + boolean onKeyLongPress(int keyCode, KeyEvent event); + boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event); + boolean onKeyUp(int keyCode, KeyEvent event); +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/IntSize.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/IntSize.java new file mode 100644 index 000000000000..ee43b1bf557b --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/IntSize.java @@ -0,0 +1,103 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.gfx.FloatSize; +import org.json.JSONException; +import org.json.JSONObject; +import java.lang.Math; + +public class IntSize { + public final int width, height; + + public IntSize(IntSize size) { width = size.width; height = size.height; } + public IntSize(int inWidth, int inHeight) { width = inWidth; height = inHeight; } + + public IntSize(FloatSize size) { + width = Math.round(size.width); + height = Math.round(size.height); + } + + public IntSize(JSONObject json) { + try { + width = json.getInt("width"); + height = json.getInt("height"); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public int getArea() { + return width * height; + } + + public boolean equals(IntSize size) { + return ((size.width == width) && (size.height == height)); + } + + public boolean isPositive() { + return (width > 0 && height > 0); + } + + @Override + public String toString() { return "(" + width + "," + height + ")"; } + + public IntSize scale(float factor) { + return new IntSize(Math.round(width * factor), + Math.round(height * factor)); + } + + /* Returns the power of two that is greater than or equal to value */ + public static int nextPowerOfTwo(int value) { + // code taken from http://acius2.blogspot.com/2007/11/calculating-next-power-of-2.html + if (0 == value--) { + return 1; + } + value = (value >> 1) | value; + value = (value >> 2) | value; + value = (value >> 4) | value; + value = (value >> 8) | value; + value = (value >> 16) | value; + return value + 1; + } + + public IntSize nextPowerOfTwo() { + return new IntSize(nextPowerOfTwo(width), nextPowerOfTwo(height)); + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/Layer.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/Layer.java new file mode 100644 index 000000000000..17ecbba4058b --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/Layer.java @@ -0,0 +1,213 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * Chris Lord <chrislord.net@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.RectF; +import android.graphics.Region; +import android.util.Log; +import java.util.concurrent.locks.ReentrantLock; +import javax.microedition.khronos.opengles.GL10; +import org.mozilla.gecko.util.FloatUtils; + +public abstract class Layer { + private final ReentrantLock mTransactionLock; + private boolean mInTransaction; + private Point mNewOrigin; + private float mNewResolution; + private LayerView mView; + + protected Point mOrigin; + protected float mResolution; + + public Layer() { + mTransactionLock = new ReentrantLock(); + mOrigin = new Point(0, 0); + mResolution = 1.0f; + } + + /** + * Updates the layer. This returns false if there is still work to be done + * after this update. + */ + public final boolean update(GL10 gl, RenderContext context) { + if (mTransactionLock.isHeldByCurrentThread()) { + throw new RuntimeException("draw() called while transaction lock held by this " + + "thread?!"); + } + + if (mTransactionLock.tryLock()) { + try { + return performUpdates(gl, context); + } finally { + mTransactionLock.unlock(); + } + } + + return false; + } + + /** Subclasses override this function to draw the layer. */ + public abstract void draw(RenderContext context); + + /** Subclasses override this function to provide access to the size of the layer. */ + public abstract IntSize getSize(); + + /** Given the intrinsic size of the layer, returns the pixel boundaries of the layer rect. */ + protected RectF getBounds(RenderContext context, FloatSize size) { + float scaleFactor = context.zoomFactor / mResolution; + float x = mOrigin.x * scaleFactor, y = mOrigin.y * scaleFactor; + float width = size.width * scaleFactor, height = size.height * scaleFactor; + return new RectF(x, y, x + width, y + height); + } + + /** + * Returns the region of the layer that is considered valid. The default + * implementation of this will return the bounds of the layer, but this + * may be overridden. + */ + public Region getValidRegion(RenderContext context) { + return new Region(RectUtils.round(getBounds(context, new FloatSize(getSize())))); + } + + /** + * Call this before modifying the layer. Note that, for TileLayers, "modifying the layer" + * includes altering the underlying CairoImage in any way. Thus you must call this function + * before modifying the byte buffer associated with this layer. + * + * This function may block, so you should never call this on the main UI thread. + */ + public void beginTransaction(LayerView aView) { + if (mTransactionLock.isHeldByCurrentThread()) + throw new RuntimeException("Nested transactions are not supported"); + mTransactionLock.lock(); + mView = aView; + mInTransaction = true; + mNewResolution = mResolution; + } + + public void beginTransaction() { + beginTransaction(null); + } + + /** Call this when you're done modifying the layer. */ + public void endTransaction() { + if (!mInTransaction) + throw new RuntimeException("endTransaction() called outside a transaction"); + mInTransaction = false; + mTransactionLock.unlock(); + + if (mView != null) + mView.requestRender(); + } + + /** Returns true if the layer is currently in a transaction and false otherwise. */ + protected boolean inTransaction() { + return mInTransaction; + } + + /** Returns the current layer origin. */ + public Point getOrigin() { + return mOrigin; + } + + /** Sets the origin. Only valid inside a transaction. */ + public void setOrigin(Point newOrigin) { + if (!mInTransaction) + throw new RuntimeException("setOrigin() is only valid inside a transaction"); + mNewOrigin = newOrigin; + } + + /** Returns the current layer's resolution. */ + public float getResolution() { + return mResolution; + } + + /** + * Sets the layer resolution. This value is used to determine how many pixels per + * device pixel this layer was rendered at. This will be reflected by scaling by + * the reciprocal of the resolution in the layer's transform() function. + * Only valid inside a transaction. */ + public void setResolution(float newResolution) { + if (!mInTransaction) + throw new RuntimeException("setResolution() is only valid inside a transaction"); + mNewResolution = newResolution; + } + + /** + * Subclasses may override this method to perform custom layer updates. This will be called + * with the transaction lock held. Subclass implementations of this method must call the + * superclass implementation. Returns false if there is still work to be done after this + * update is complete. + */ + protected boolean performUpdates(GL10 gl, RenderContext context) { + if (mNewOrigin != null) { + mOrigin = mNewOrigin; + mNewOrigin = null; + } + if (mNewResolution != 0.0f) { + mResolution = mNewResolution; + mNewResolution = 0.0f; + } + + return true; + } + + public static class RenderContext { + public final RectF viewport; + public final FloatSize pageSize; + public final float zoomFactor; + + public RenderContext(RectF aViewport, FloatSize aPageSize, float aZoomFactor) { + viewport = aViewport; + pageSize = aPageSize; + zoomFactor = aZoomFactor; + } + + public boolean fuzzyEquals(RenderContext other) { + if (other == null) { + return false; + } + return RectUtils.fuzzyEquals(viewport, other.viewport) + && pageSize.fuzzyEquals(other.pageSize) + && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor); + } + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerClient.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerClient.java new file mode 100644 index 000000000000..4f461088203e --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerClient.java @@ -0,0 +1,81 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +/** + * A layer client provides tiles and manages other information used by the layer controller. + */ +public abstract class LayerClient { + private LayerController mLayerController; + + public abstract void geometryChanged(); + + public abstract void viewportSizeChanged(); + + protected abstract void render(); + + public LayerController getLayerController() { + return mLayerController; + } + + public void setLayerController(LayerController layerController) { + mLayerController = layerController; + } + + /** + * A utility function for calling Layer.beginTransaction with the + * appropriate LayerView. + */ + public void beginTransaction(Layer aLayer) { + if (mLayerController != null) { + LayerView view = mLayerController.getView(); + if (view != null) { + aLayer.beginTransaction(view); + return; + } + } + + aLayer.beginTransaction(); + } + + // Included for symmetry. + public void endTransaction(Layer aLayer) { + aLayer.endTransaction(); + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerController.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerController.java new file mode 100644 index 000000000000..fb0823823877 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerController.java @@ -0,0 +1,525 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * Chris Lord <chrislord.net@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View.OnTouchListener; +import android.view.ViewConfiguration; + +import org.mozilla.gecko.ui.PanZoomController; +import org.mozilla.gecko.ui.SimpleScaleGestureDetector; + +import java.util.Timer; +import java.util.TimerTask; + +//import org.mozilla.gecko.GeckoApp; +//import org.mozilla.gecko.GeckoEvent; +//import org.mozilla.gecko.Tabs; +//import org.mozilla.gecko.Tab; + +/** + * The layer controller manages a tile that represents the visible page. It does panning and + * zooming natively by delegating to a panning/zooming controller. Touch events can be dispatched + * to a higher-level view. + * <p/> + * Many methods require that the monitor be held, with a synchronized (controller) { ... } block. + */ +public class LayerController /*implements Tabs.OnTabsChangedListener*/ { + /* The extra area on the sides of the page that we want to buffer to help with + * smooth, asynchronous scrolling. Depending on a device's support for NPOT + * textures, this may be rounded up to the nearest power of two. + */ + public static final IntSize MIN_BUFFER = new IntSize(512, 1024); + private static final String LOGTAG = "GeckoLayerController"; + /* If the visible rect is within the danger zone (measured in pixels from each edge of a tile), + * we start aggressively redrawing to minimize checkerboarding. */ + private static final int DANGER_ZONE_X = 75; + private static final int DANGER_ZONE_Y = 150; + private Layer mRootLayer; /* The root layer. */ + private LayerView mView; /* The main rendering view. */ + private Context mContext; /* The current context. */ + /* + * The panning and zooming controller, which interprets pan and zoom gestures for us and + * updates our visible rect appropriately. + */ + private ViewportMetrics mViewportMetrics; /* The current viewport metrics. */ + private boolean mWaitForTouchListeners; + private PanZoomController mPanZoomController; + private OnTouchListener mOnTouchListener; /* The touch listener. */ + private LayerClient mLayerClient; /* The layer client. */ + /* The new color for the checkerboard. */ + private int mCheckerboardColor; + private boolean mCheckerboardShouldShowChecks; + private boolean mForceRedraw; + /* The time limit for pages to respond with preventDefault on touchevents + * before we begin panning the page */ + private int mTimeout = 200; + + private boolean allowDefaultActions = true; + private Timer allowDefaultTimer = null; + private PointF initialTouchLocation = null; + + public LayerController(Context context) { + mContext = context; + + mForceRedraw = true; + mViewportMetrics = new ViewportMetrics(); + mPanZoomController = new PanZoomController(this); + mView = new LayerView(context, this); + + //Tabs.getInstance().registerOnTabsChangedListener(this); + + ViewConfiguration vc = ViewConfiguration.get(mContext); + mTimeout = vc.getLongPressTimeout(); + } + + public void onDestroy() { + //Tabs.getInstance().unregisterOnTabsChangedListener(this); + } + + public void setForceRedraw() { + mForceRedraw = true; + } + + public LayerClient getLayerClient() { + return mLayerClient; + } + + public void setLayerClient(LayerClient layerClient) { + mLayerClient = layerClient; + layerClient.setLayerController(this); + } + + public Layer getRoot() { + return mRootLayer; + } + + public void setRoot(Layer layer) { + mRootLayer = layer; + } + + public LayerView getView() { + return mView; + } + + public Context getContext() { + return mContext; + } + + public ViewportMetrics getViewportMetrics() { + return mViewportMetrics; + } + + /** + * Sets the entire viewport metrics at once. This function does not notify the layer client or + * the pan/zoom controller, so you will need to call notifyLayerClientOfGeometryChange() or + * notifyPanZoomControllerOfGeometryChange() after calling this. You must hold the monitor + * while calling this. + */ + public void setViewportMetrics(ViewportMetrics viewport) { + mViewportMetrics = new ViewportMetrics(viewport); + Log.d(LOGTAG, "setViewportMetrics: " + mViewportMetrics); + // this function may or may not be called on the UI thread, + // but repositionPluginViews must only be called on the UI thread. + //GeckoApp.mAppContext.runOnUiThread(new Runnable() { + // public void run() { + // GeckoApp.mAppContext.repositionPluginViews(false); + // } + //}); + mView.requestRender(); + } + + public RectF getViewport() { + return mViewportMetrics.getViewport(); + } + + public FloatSize getViewportSize() { + return mViewportMetrics.getSize(); + } + + /** + * The view calls this function to indicate that the viewport changed size. It must hold the + * monitor while calling it. + * <p/> + * TODO: Refactor this to use an interface. Expose that interface only to the view and not + * to the layer client. That way, the layer client won't be tempted to call this, which might + * result in an infinite loop. + */ + public void setViewportSize(FloatSize size) { + // Resize the viewport, and modify its zoom factor so that the page retains proportionally + // zoomed relative to the screen. + float oldHeight = mViewportMetrics.getSize().height; + float oldWidth = mViewportMetrics.getSize().width; + float oldZoomFactor = mViewportMetrics.getZoomFactor(); + mViewportMetrics.setSize(size); + + // if the viewport got larger (presumably because the vkb went away), and the page + // is smaller than the new viewport size, increase the page size so that the panzoomcontroller + // doesn't zoom in to make it fit (bug 718270). this page size change is in anticipation of + // gecko increasing the page size to match the new viewport size, which will happen the next + // time we get a draw update. + if (size.width >= oldWidth && size.height >= oldHeight) { + FloatSize pageSize = mViewportMetrics.getPageSize(); + if (pageSize.width < size.width || pageSize.height < size.height) { + mViewportMetrics.setPageSize(new FloatSize(Math.max(pageSize.width, size.width), + Math.max(pageSize.height, size.height))); + } + } + + PointF newFocus = new PointF(size.width / 2.0f, size.height / 2.0f); + float newZoomFactor = size.width * oldZoomFactor / oldWidth; + mViewportMetrics.scaleTo(newZoomFactor, newFocus); + + Log.d(LOGTAG, "setViewportSize: " + mViewportMetrics); + setForceRedraw(); + + if (mLayerClient != null) + mLayerClient.viewportSizeChanged(); + + notifyLayerClientOfGeometryChange(); + mPanZoomController.abortAnimation(); + mView.requestRender(); + } + + public FloatSize getPageSize() { + return mViewportMetrics.getPageSize(); + } + + /** + * Sets the current page size. You must hold the monitor while calling this. + */ + public void setPageSize(FloatSize size) { + if (mViewportMetrics.getPageSize().fuzzyEquals(size)) + return; + + mViewportMetrics.setPageSize(size); + Log.d(LOGTAG, "setPageSize: " + mViewportMetrics); + + // Page size is owned by the LayerClient, so no need to notify it of + // this change. + + mView.post(new Runnable() { + public void run() { + mPanZoomController.pageSizeUpdated(); + mView.requestRender(); + } + }); + } + + public PointF getOrigin() { + return mViewportMetrics.getOrigin(); + } + + public float getZoomFactor() { + return mViewportMetrics.getZoomFactor(); + } + + public Bitmap getBackgroundPattern() { + return getDrawable("background"); + } + + public Bitmap getShadowPattern() { + return getDrawable("shadow"); + } + + public PanZoomController getPanZoomController() { + return mPanZoomController; + } + + public GestureDetector.OnGestureListener getGestureListener() { + return mPanZoomController; + } + + public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() { + return mPanZoomController; + } + + public GestureDetector.OnDoubleTapListener getDoubleTapListener() { + return mPanZoomController; + } + + public Bitmap getDrawable(String name) { + Resources resources = mContext.getResources(); + int resourceID = resources.getIdentifier(name, "drawable", mContext.getPackageName()); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; + return BitmapFactory.decodeResource(mContext.getResources(), resourceID, options); + } + + public Bitmap getDrawable16(String name) { + Resources resources = mContext.getResources(); + int resourceID = resources.getIdentifier(name, "drawable", mContext.getPackageName()); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; + options.inPreferredConfig = Bitmap.Config.RGB_565; + return BitmapFactory.decodeResource(mContext.getResources(), resourceID, options); + } + + /** + * Scrolls the viewport by the given offset. You must hold the monitor while calling this. + */ + public void scrollBy(PointF point) { + PointF origin = mViewportMetrics.getOrigin(); + origin.offset(point.x, point.y); + mViewportMetrics.setOrigin(origin); + Log.d(LOGTAG, "scrollBy: " + mViewportMetrics); + + notifyLayerClientOfGeometryChange(); + //GeckoApp.mAppContext.repositionPluginViews(false); + mView.requestRender(); + } + + /** + * Scales the viewport, keeping the given focus point in the same place before and after the + * scale operation. You must hold the monitor while calling this. + */ + public void scaleWithFocus(float zoomFactor, PointF focus) { + mViewportMetrics.scaleTo(zoomFactor, focus); + Log.d(LOGTAG, "scaleWithFocus: " + mViewportMetrics + "; zf=" + zoomFactor); + + // We assume the zoom level will only be modified by the + // PanZoomController, so no need to notify it of this change. + notifyLayerClientOfGeometryChange(); + //GeckoApp.mAppContext.repositionPluginViews(false); + mView.requestRender(); + } + + public boolean post(Runnable action) { + return mView.post(action); + } + + public void setOnTouchListener(OnTouchListener onTouchListener) { + mOnTouchListener = onTouchListener; + } + + /** + * The view as well as the controller itself use this method to notify the layer client that + * the geometry changed. + */ + public void notifyLayerClientOfGeometryChange() { + if (mLayerClient != null) + mLayerClient.geometryChanged(); + } + + /** + * Aborts any pan/zoom animation that is currently in progress. + */ + public void abortPanZoomAnimation() { + if (mPanZoomController != null) { + mView.post(new Runnable() { + public void run() { + mPanZoomController.abortAnimation(); + } + }); + } + } + + /** + * Returns true if this controller is fine with performing a redraw operation or false if it + * would prefer that the action didn't take place. + */ + public boolean getRedrawHint() { + if (mForceRedraw) { + mForceRedraw = false; + return true; + } + + return aboutToCheckerboard() && mPanZoomController.getRedrawHint(); + } + + private RectF getTileRect() { + if (mRootLayer == null) + return new RectF(); + + float x = mRootLayer.getOrigin().x, y = mRootLayer.getOrigin().y; + IntSize layerSize = mRootLayer.getSize(); + return new RectF(x, y, x + layerSize.width, y + layerSize.height); + } + + // Returns true if a checkerboard is about to be visible. + private boolean aboutToCheckerboard() { + // Increase the size of the viewport (and clamp to page boundaries), and + // intersect it with the tile's displayport to determine whether we're + // close to checkerboarding. + FloatSize pageSize = getPageSize(); + RectF adjustedViewport = RectUtils.expand(getViewport(), DANGER_ZONE_X, DANGER_ZONE_Y); + if (adjustedViewport.top < 0) adjustedViewport.top = 0; + if (adjustedViewport.left < 0) adjustedViewport.left = 0; + if (adjustedViewport.right > pageSize.width) adjustedViewport.right = pageSize.width; + if (adjustedViewport.bottom > pageSize.height) adjustedViewport.bottom = pageSize.height; + + return !getTileRect().contains(adjustedViewport); + } + + /** + * Converts a point from layer view coordinates to layer coordinates. In other words, given a + * point measured in pixels from the top left corner of the layer view, returns the point in + * pixels measured from the top left corner of the root layer, in the coordinate system of the + * layer itself. This method is used by the viewport controller as part of the process of + * translating touch events to Gecko's coordinate system. + */ + public PointF convertViewPointToLayerPoint(PointF viewPoint) { + if (mRootLayer == null) + return null; + + // Undo the transforms. + PointF origin = mViewportMetrics.getOrigin(); + PointF newPoint = new PointF(origin.x, origin.y); + newPoint.offset(viewPoint.x, viewPoint.y); + + Point rootOrigin = mRootLayer.getOrigin(); + newPoint.offset(-rootOrigin.x, -rootOrigin.y); + + return newPoint; + } + + /* + * Gesture detection. This is handled only at a high level in this class; we dispatch to the + * pan/zoom controller to do the dirty work. + */ + public boolean onTouchEvent(MotionEvent event) { + int action = event.getAction(); + PointF point = new PointF(event.getX(), event.getY()); + + // this will only match the first touchstart in a series + if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + initialTouchLocation = point; + allowDefaultActions = !mWaitForTouchListeners; + + // if we have a timer, this may be a double tap, + // cancel the current timer but don't clear the event queue + if (allowDefaultTimer != null) { + allowDefaultTimer.cancel(); + } else { + // if we don't have a timer, make sure we remove any old events + mView.clearEventQueue(); + } + allowDefaultTimer = new Timer(); + allowDefaultTimer.schedule(new TimerTask() { + public void run() { + post(new Runnable() { + public void run() { + preventPanning(false); + } + }); + } + }, mTimeout); + } + + // After the initial touch, ignore touch moves until they exceed a minimum distance. + if (initialTouchLocation != null && (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_MOVE) { + if (PointUtils.subtract(point, initialTouchLocation).length() > PanZoomController.PAN_THRESHOLD) { + initialTouchLocation = null; + } else { + return !allowDefaultActions; + } + } + + // send the event to content + if (mOnTouchListener != null) + mOnTouchListener.onTouch(mView, event); + + return !allowDefaultActions; + } + + public void preventPanning(boolean aValue) { + if (allowDefaultTimer != null) { + allowDefaultTimer.cancel(); + allowDefaultTimer = null; + } + if (aValue == allowDefaultActions) { + allowDefaultActions = !aValue; + + if (aValue) { + mView.clearEventQueue(); + mPanZoomController.cancelTouch(); + } else { + mView.processEventQueue(); + } + } + } + + /*public void onTabChanged(Tab tab, Tabs.TabEvents msg) { + if ((Tabs.getInstance().isSelectedTab(tab) && msg == Tabs.TabEvents.STOP) || msg == Tabs.TabEvents.SELECTED) { + mWaitForTouchListeners = tab.getHasTouchListeners(); + } + }*/ + public void setWaitForTouchListeners(boolean aValue) { + mWaitForTouchListeners = aValue; + } + + /** + * Retrieves whether we should show checkerboard checks or not. + */ + public boolean checkerboardShouldShowChecks() { + return mCheckerboardShouldShowChecks; + } + + /** + * Retrieves the color that the checkerboard should be. + */ + public int getCheckerboardColor() { + return mCheckerboardColor; + } + + /** + * Sets a new color for the checkerboard. + */ + public void setCheckerboardColor(int newColor) { + mCheckerboardColor = newColor; + mView.requestRender(); + } + + /** + * Sets whether or not the checkerboard should show checkmarks. + */ + public void setCheckerboardShowChecks(boolean showChecks) { + mCheckerboardShouldShowChecks = showChecks; + mView.requestRender(); + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java new file mode 100644 index 000000000000..9081a19e4ecb --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java @@ -0,0 +1,508 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * Chris Lord <chrislord.net@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.gfx.BufferedCairoImage; +import org.mozilla.gecko.gfx.IntSize; +import org.mozilla.gecko.gfx.Layer.RenderContext; +import org.mozilla.gecko.gfx.LayerController; +import org.mozilla.gecko.gfx.LayerView; +import org.mozilla.gecko.gfx.NinePatchTileLayer; +import org.mozilla.gecko.gfx.SingleTileLayer; +import org.mozilla.gecko.gfx.TextureReaper; +import org.mozilla.gecko.gfx.TextureGenerator; +import org.mozilla.gecko.gfx.TextLayer; +import org.mozilla.gecko.gfx.TileLayer; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.graphics.RegionIterator; +import android.opengl.GLSurfaceView; +import android.os.SystemClock; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.WindowManager; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; +import java.nio.IntBuffer; +import java.util.ArrayList; + +/** + * The layer renderer implements the rendering logic for a layer view. + */ +public class LayerRenderer implements GLSurfaceView.Renderer { + private static final String LOGTAG = "GeckoLayerRenderer"; + private static final String PROFTAG = "GeckoLayerRendererProf"; + + /* + * The amount of time a frame is allowed to take to render before we declare it a dropped + * frame. + */ + private static final int MAX_FRAME_TIME = 16; /* 1000 ms / 60 FPS */ + + private static final int FRAME_RATE_METER_WIDTH = 64; + private static final int FRAME_RATE_METER_HEIGHT = 32; + + private final LayerView mView; + private final SingleTileLayer mBackgroundLayer; + private final CheckerboardImage mCheckerboardImage; + private final SingleTileLayer mCheckerboardLayer; + private final NinePatchTileLayer mShadowLayer; + private final TextLayer mFrameRateLayer; + private final ScrollbarLayer mHorizScrollLayer; + private final ScrollbarLayer mVertScrollLayer; + private final FadeRunnable mFadeRunnable; + private RenderContext mLastPageContext; + private int mMaxTextureSize; + + private ArrayList<Layer> mExtraLayers = new ArrayList<Layer>(); + + // Dropped frames display + private int[] mFrameTimings; + private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames; + private boolean mShowFrameRate; + + // Render profiling output + private int mFramesRendered; + private float mCompleteFramesRendered; + private boolean mProfileRender; + private long mProfileOutputTime; + + /* Used by robocop for testing purposes */ + private IntBuffer mPixelBuffer; + + public LayerRenderer(LayerView view) { + mView = view; + + LayerController controller = view.getController(); + + CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern()); + mBackgroundLayer = new SingleTileLayer(true, backgroundImage); + + mCheckerboardImage = new CheckerboardImage(); + mCheckerboardLayer = new SingleTileLayer(true, mCheckerboardImage); + + CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern()); + mShadowLayer = new NinePatchTileLayer(shadowImage); + + IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT); + mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--"); + + mHorizScrollLayer = ScrollbarLayer.create(false); + mVertScrollLayer = ScrollbarLayer.create(true); + mFadeRunnable = new FadeRunnable(); + + mFrameTimings = new int[60]; + mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0; + mShowFrameRate = false; + } + + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + checkMonitoringEnabled(); + + gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); + gl.glDisable(GL10.GL_DITHER); + gl.glEnable(GL10.GL_TEXTURE_2D); + + int maxTextureSizeResult[] = new int[1]; + gl.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0); + mMaxTextureSize = maxTextureSizeResult[0]; + + TextureGenerator.get().fill(); + } + + public int getMaxTextureSize() { + return mMaxTextureSize; + } + + public void addLayer(Layer layer) { + LayerController controller = mView.getController(); + + synchronized (controller) { + if (mExtraLayers.contains(layer)) { + mExtraLayers.remove(layer); + } + + mExtraLayers.add(layer); + } + } + + public void removeLayer(Layer layer) { + LayerController controller = mView.getController(); + + synchronized (controller) { + mExtraLayers.remove(layer); + } + } + + /** + * Called whenever a new frame is about to be drawn. + */ + public void onDrawFrame(GL10 gl) { + long frameStartTime = SystemClock.uptimeMillis(); + + TextureReaper.get().reap(gl); + TextureGenerator.get().fill(); + + LayerController controller = mView.getController(); + RenderContext screenContext = createScreenContext(); + + boolean updated = true; + + synchronized (controller) { + Layer rootLayer = controller.getRoot(); + RenderContext pageContext = createPageContext(); + + if (!pageContext.fuzzyEquals(mLastPageContext)) { + // the viewport or page changed, so show the scrollbars again + // as per UX decision + mVertScrollLayer.unfade(); + mHorizScrollLayer.unfade(); + mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY); + } else if (mFadeRunnable.timeToFade()) { + boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade(); + if (stillFading) { + mFadeRunnable.scheduleNextFadeFrame(); + } + } + mLastPageContext = pageContext; + + /* Update layers. */ + if (rootLayer != null) updated &= rootLayer.update(gl, pageContext); + updated &= mBackgroundLayer.update(gl, screenContext); + updated &= mShadowLayer.update(gl, pageContext); + updateCheckerboardLayer(gl, screenContext); + updated &= mFrameRateLayer.update(gl, screenContext); + updated &= mVertScrollLayer.update(gl, pageContext); + updated &= mHorizScrollLayer.update(gl, pageContext); + + for (Layer layer : mExtraLayers) + updated &= layer.update(gl, pageContext); + + /* Draw the background. */ + mBackgroundLayer.draw(screenContext); + + /* Draw the drop shadow, if we need to. */ + Rect pageRect = getPageRect(); + RectF untransformedPageRect = new RectF(0.0f, 0.0f, pageRect.width(), + pageRect.height()); + if (!untransformedPageRect.contains(controller.getViewport())) + mShadowLayer.draw(pageContext); + + /* Draw the checkerboard. */ + Rect scissorRect = transformToScissorRect(pageRect); + gl.glEnable(GL10.GL_SCISSOR_TEST); + gl.glScissor(scissorRect.left, scissorRect.top, + scissorRect.width(), scissorRect.height()); + + mCheckerboardLayer.draw(screenContext); + + /* Draw the layer the client added to us. */ + if (rootLayer != null) + rootLayer.draw(pageContext); + + gl.glDisable(GL10.GL_SCISSOR_TEST); + + /* Draw any extra layers that were added (likely plugins) */ + for (Layer layer : mExtraLayers) + layer.draw(pageContext); + + /* Draw the vertical scrollbar. */ + IntSize screenSize = new IntSize(controller.getViewportSize()); + if (pageRect.height() > screenSize.height) + mVertScrollLayer.draw(pageContext); + + /* Draw the horizontal scrollbar. */ + if (pageRect.width() > screenSize.width) + mHorizScrollLayer.draw(pageContext); + + /* Measure how much of the screen is checkerboarding */ + if ((rootLayer != null) && + (mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) { + // Find out how much of the viewport area is valid + Rect viewport = RectUtils.round(pageContext.viewport); + Region validRegion = rootLayer.getValidRegion(pageContext); + validRegion.op(viewport, Region.Op.INTERSECT); + + float checkerboard = 0.0f; + if (!(validRegion.isRect() && validRegion.getBounds().equals(viewport))) { + int screenArea = viewport.width() * viewport.height(); + validRegion.op(viewport, Region.Op.REVERSE_DIFFERENCE); + + // XXX The assumption here is that a Region never has overlapping + // rects. This is true, as evidenced by reading the SkRegion + // source, but is not mentioned in the Android documentation, + // and so is liable to change. + // If it does change, this code will need to be reevaluated. + Rect r = new Rect(); + int checkerboardArea = 0; + for (RegionIterator i = new RegionIterator(validRegion); i.next(r);) { + checkerboardArea += r.width() * r.height(); + } + + checkerboard = checkerboardArea / (float)screenArea; + } + + PanningPerfAPI.recordCheckerboard(checkerboard); + + mCompleteFramesRendered += 1.0f - checkerboard; + mFramesRendered ++; + + if (frameStartTime - mProfileOutputTime > 1000) { + mProfileOutputTime = frameStartTime; + printCheckerboardStats(); + } + } + } + + /* Draw the FPS. */ + if (mShowFrameRate) { + updateDroppedFrames(frameStartTime); + + try { + gl.glEnable(GL10.GL_BLEND); + gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); + mFrameRateLayer.draw(screenContext); + } finally { + gl.glDisable(GL10.GL_BLEND); + } + } + + // If a layer update requires further work, schedule another redraw + if (!updated) + mView.requestRender(); + + PanningPerfAPI.recordFrameTime(); + + /* Used by robocop for testing purposes */ + IntBuffer pixelBuffer = mPixelBuffer; + if (updated && pixelBuffer != null) { + synchronized (pixelBuffer) { + pixelBuffer.position(0); + gl.glReadPixels(0, 0, (int)screenContext.viewport.width(), (int)screenContext.viewport.height(), GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuffer); + pixelBuffer.notify(); + } + } + } + + private void printCheckerboardStats() { + Log.d(PROFTAG, "Frames rendered over last 1000ms: " + mCompleteFramesRendered + "/" + mFramesRendered); + mFramesRendered = 0; + mCompleteFramesRendered = 0; + } + + /** Used by robocop for testing purposes. Not for production use! */ + IntBuffer getPixels() { + IntBuffer pixelBuffer = IntBuffer.allocate(mView.getWidth() * mView.getHeight()); + synchronized (pixelBuffer) { + mPixelBuffer = pixelBuffer; + mView.requestRender(); + try { + pixelBuffer.wait(); + } catch (InterruptedException ie) { + } + mPixelBuffer = null; + } + return pixelBuffer; + } + + private RenderContext createScreenContext() { + LayerController layerController = mView.getController(); + IntSize viewportSize = new IntSize(layerController.getViewportSize()); + RectF viewport = new RectF(0.0f, 0.0f, viewportSize.width, viewportSize.height); + FloatSize pageSize = new FloatSize(layerController.getPageSize()); + return new RenderContext(viewport, pageSize, 1.0f); + } + + private RenderContext createPageContext() { + LayerController layerController = mView.getController(); + + Rect viewport = new Rect(); + layerController.getViewport().round(viewport); + + FloatSize pageSize = new FloatSize(layerController.getPageSize()); + float zoomFactor = layerController.getZoomFactor(); + return new RenderContext(new RectF(viewport), pageSize, zoomFactor); + } + + private Rect getPageRect() { + LayerController controller = mView.getController(); + + Point origin = PointUtils.round(controller.getOrigin()); + IntSize pageSize = new IntSize(controller.getPageSize()); + + origin.negate(); + + return new Rect(origin.x, origin.y, + origin.x + pageSize.width, origin.y + pageSize.height); + } + + private Rect transformToScissorRect(Rect rect) { + LayerController controller = mView.getController(); + IntSize screenSize = new IntSize(controller.getViewportSize()); + + int left = Math.max(0, rect.left); + int top = Math.max(0, rect.top); + int right = Math.min(screenSize.width, rect.right); + int bottom = Math.min(screenSize.height, rect.bottom); + + return new Rect(left, screenSize.height - bottom, right, + (screenSize.height - bottom) + (bottom - top)); + } + + public void onSurfaceChanged(GL10 gl, final int width, final int height) { + gl.glViewport(0, 0, width, height); + + // updating the state in the view/controller/client should be + // done on the main UI thread, not the GL renderer thread + mView.post(new Runnable() { + public void run() { + mView.setViewportSize(new IntSize(width, height)); + moveFrameRateLayer(width, height); + } + }); + + /* TODO: Throw away tile images? */ + } + + private void updateDroppedFrames(long frameStartTime) { + int frameElapsedTime = (int)(SystemClock.uptimeMillis() - frameStartTime); + + /* Update the running statistics. */ + mFrameTimingsSum -= mFrameTimings[mCurrentFrame]; + mFrameTimingsSum += frameElapsedTime; + mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME; + mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME; + + mFrameTimings[mCurrentFrame] = frameElapsedTime; + mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length; + + int averageTime = mFrameTimingsSum / mFrameTimings.length; + mFrameRateLayer.beginTransaction(); + try { + mFrameRateLayer.setText(averageTime + " ms/" + mDroppedFrames); + } finally { + mFrameRateLayer.endTransaction(); + } + } + + /* Given the new dimensions for the surface, moves the frame rate layer appropriately. */ + private void moveFrameRateLayer(int width, int height) { + mFrameRateLayer.beginTransaction(); + try { + Point origin = new Point(width - FRAME_RATE_METER_WIDTH - 8, + height - FRAME_RATE_METER_HEIGHT + 8); + mFrameRateLayer.setOrigin(origin); + } finally { + mFrameRateLayer.endTransaction(); + } + } + + private void checkMonitoringEnabled() { + /* Do this I/O off the main thread to minimize its impact on startup time. */ + new Thread(new Runnable() { + @Override + public void run() { + Context context = mView.getContext(); + SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0); + mShowFrameRate = preferences.getBoolean("showFrameRate", false); + mProfileRender = Log.isLoggable(PROFTAG, Log.DEBUG); + } + }).start(); + } + + private void updateCheckerboardLayer(GL10 gl, RenderContext renderContext) { + int checkerboardColor = mView.getController().getCheckerboardColor(); + boolean showChecks = mView.getController().checkerboardShouldShowChecks(); + if (checkerboardColor == mCheckerboardImage.getColor() && + showChecks == mCheckerboardImage.getShowChecks()) { + return; + } + + mCheckerboardLayer.beginTransaction(); + try { + mCheckerboardImage.update(showChecks, checkerboardColor); + mCheckerboardLayer.invalidate(); + } finally { + mCheckerboardLayer.endTransaction(); + } + + mCheckerboardLayer.update(gl, renderContext); + } + + class FadeRunnable implements Runnable { + private boolean mStarted; + private long mRunAt; + + void scheduleStartFade(long delay) { + mRunAt = SystemClock.elapsedRealtime() + delay; + if (!mStarted) { + mView.postDelayed(this, delay); + mStarted = true; + } + } + + void scheduleNextFadeFrame() { + if (mStarted) { + Log.e(LOGTAG, "scheduleNextFadeFrame() called while scheduled for starting fade"); + } + mView.postDelayed(this, 1000L / 60L); // request another frame at 60fps + } + + boolean timeToFade() { + return !mStarted; + } + + public void run() { + long timeDelta = mRunAt - SystemClock.elapsedRealtime(); + if (timeDelta > 0) { + // the run-at time was pushed back, so reschedule + mView.postDelayed(this, timeDelta); + } else { + // reached the run-at time, execute + mStarted = false; + mView.requestRender(); + } + } + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerView.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerView.java new file mode 100644 index 000000000000..2b63f1df15ea --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerView.java @@ -0,0 +1,215 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.gfx.FloatSize; +import org.mozilla.gecko.gfx.InputConnectionHandler; +import org.mozilla.gecko.gfx.LayerController; +import org.mozilla.gecko.ui.SimpleScaleGestureDetector; +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.util.Log; +import java.util.LinkedList; + +/** + * A view rendered by the layer compositor. + * + * This view delegates to LayerRenderer to actually do the drawing. Its role is largely that of a + * mediator between the LayerRenderer and the LayerController. + */ +public class LayerView extends GLSurfaceView { + private Context mContext; + private LayerController mController; + private InputConnectionHandler mInputConnectionHandler; + private LayerRenderer mRenderer; + private GestureDetector mGestureDetector; + private SimpleScaleGestureDetector mScaleGestureDetector; + private long mRenderTime; + private boolean mRenderTimeReset; + private static String LOGTAG = "GeckoLayerView"; + /* List of events to be processed if the page does not prevent them. Should only be touched on the main thread */ + private LinkedList<MotionEvent> mEventQueue = new LinkedList<MotionEvent>(); + + public LayerView(Context context, LayerController controller) { + super(context); + + mContext = context; + mController = controller; + mRenderer = new LayerRenderer(this); + setRenderer(mRenderer); + setRenderMode(RENDERMODE_WHEN_DIRTY); + mGestureDetector = new GestureDetector(context, controller.getGestureListener()); + mScaleGestureDetector = + new SimpleScaleGestureDetector(controller.getScaleGestureListener()); + mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener()); + mInputConnectionHandler = null; + + setFocusable(true); + setFocusableInTouchMode(true); + } + + private void addToEventQueue(MotionEvent event) { + MotionEvent copy = MotionEvent.obtain(event); + mEventQueue.add(copy); + } + + public void processEventQueue() { + MotionEvent event = mEventQueue.poll(); + while(event != null) { + processEvent(event); + event = mEventQueue.poll(); + } + } + + public void clearEventQueue() { + mEventQueue.clear(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mController.onTouchEvent(event)) { + addToEventQueue(event); + return true; + } + return processEvent(event); + } + + private boolean processEvent(MotionEvent event) { + if (mGestureDetector.onTouchEvent(event)) + return true; + mScaleGestureDetector.onTouchEvent(event); + if (mScaleGestureDetector.isInProgress()) + return true; + mController.getPanZoomController().onTouchEvent(event); + return true; + } + + public LayerController getController() { return mController; } + + /** The LayerRenderer calls this to indicate that the window has changed size. */ + public void setViewportSize(IntSize size) { + mController.setViewportSize(new FloatSize(size)); + } + + public void setInputConnectionHandler(InputConnectionHandler handler) { + mInputConnectionHandler = handler; + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + if (mInputConnectionHandler != null) + return mInputConnectionHandler.onCreateInputConnection(outAttrs); + return null; + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (mInputConnectionHandler != null) + return mInputConnectionHandler.onKeyPreIme(keyCode, event); + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (mInputConnectionHandler != null) + return mInputConnectionHandler.onKeyDown(keyCode, event); + return false; + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + if (mInputConnectionHandler != null) + return mInputConnectionHandler.onKeyLongPress(keyCode, event); + return false; + } + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + if (mInputConnectionHandler != null) + return mInputConnectionHandler.onKeyMultiple(keyCode, repeatCount, event); + return false; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (mInputConnectionHandler != null) + return mInputConnectionHandler.onKeyUp(keyCode, event); + return false; + } + + @Override + public void requestRender() { + super.requestRender(); + + synchronized(this) { + if (!mRenderTimeReset) { + mRenderTimeReset = true; + mRenderTime = System.nanoTime(); + } + } + } + + public void addLayer(Layer layer) { + mRenderer.addLayer(layer); + } + + public void removeLayer(Layer layer) { + mRenderer.removeLayer(layer); + } + + /** + * Returns the time elapsed between the first call of requestRender() after + * the last call of getRenderTime(), in nanoseconds. + */ + public long getRenderTime() { + synchronized(this) { + mRenderTimeReset = false; + return System.nanoTime() - mRenderTime; + } + } + + public int getMaxTextureSize() { + return mRenderer.getMaxTextureSize(); + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/MultiTileLayer.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/MultiTileLayer.java new file mode 100644 index 000000000000..94a605f35572 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/MultiTileLayer.java @@ -0,0 +1,284 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011-2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Chris Lord <chrislord.net@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.gfx.CairoImage; +import org.mozilla.gecko.gfx.IntSize; +import org.mozilla.gecko.gfx.SingleTileLayer; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.Log; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import javax.microedition.khronos.opengles.GL10; + +/** + * Encapsulates the logic needed to draw a layer made of multiple tiles. + * + * TODO: Support repeating. + */ +public class MultiTileLayer extends Layer { + private static final String LOGTAG = "GeckoMultiTileLayer"; + + private final CairoImage mImage; + private IntSize mTileSize; + private IntSize mBufferSize; + private final ArrayList<SingleTileLayer> mTiles; + + public MultiTileLayer(CairoImage image, IntSize tileSize) { + super(); + + mImage = image; + mTileSize = tileSize; + mBufferSize = new IntSize(0, 0); + mTiles = new ArrayList<SingleTileLayer>(); + } + + public void invalidate(Rect dirtyRect) { + if (!inTransaction()) + throw new RuntimeException("invalidate() is only valid inside a transaction"); + + int x = 0, y = 0; + IntSize size = getSize(); + for (SingleTileLayer layer : mTiles) { + Rect tileRect = new Rect(x, y, x + mTileSize.width, y + mTileSize.height); + + if (tileRect.intersect(dirtyRect)) { + tileRect.offset(-x, -y); + layer.invalidate(tileRect); + } + + x += mTileSize.width; + if (x >= size.width) { + x = 0; + y += mTileSize.height; + } + } + } + + public void invalidate() { + for (SingleTileLayer layer : mTiles) + layer.invalidate(); + } + + @Override + public IntSize getSize() { + return mImage.getSize(); + } + + private void validateTiles() { + IntSize size = getSize(); + + if (size.equals(mBufferSize)) + return; + + // Regenerate tiles + mTiles.clear(); + int offset = 0; + final int format = mImage.getFormat(); + final ByteBuffer buffer = mImage.getBuffer().slice(); + final int bpp = CairoUtils.bitsPerPixelForCairoFormat(format) / 8; + for (int y = 0; y < size.height; y += mTileSize.height) { + for (int x = 0; x < size.width; x += mTileSize.width) { + // Create a CairoImage implementation that returns a + // tile from the parent CairoImage. It's assumed that + // the tiles are stored in series. + final IntSize layerSize = + new IntSize(Math.min(mTileSize.width, size.width - x), + Math.min(mTileSize.height, size.height - y)); + final int tileOffset = offset; + + CairoImage subImage = new CairoImage() { + @Override + public ByteBuffer getBuffer() { + // Create a ByteBuffer that shares the data of the original + // buffer, but is positioned and limited so that only the + // tile data is accessible. + buffer.position(tileOffset); + ByteBuffer tileBuffer = buffer.slice(); + tileBuffer.limit(layerSize.getArea() * bpp); + + return tileBuffer; + } + + @Override + public IntSize getSize() { + return layerSize; + } + + @Override + public int getFormat() { + return format; + } + }; + + mTiles.add(new SingleTileLayer(subImage)); + offset += layerSize.getArea() * bpp; + } + } + + // Set tile origins and resolution + refreshTileMetrics(getOrigin(), getResolution(), false); + + mBufferSize = size; + } + + @Override + protected boolean performUpdates(GL10 gl, RenderContext context) { + super.performUpdates(gl, context); + + validateTiles(); + + // Iterate over the tiles and decide which ones we'll be drawing + int dirtyTiles = 0; + boolean screenUpdateDone = false; + SingleTileLayer firstDirtyTile = null; + for (SingleTileLayer layer : mTiles) { + // First do a non-texture update to make sure coordinates are + // up-to-date. + boolean invalid = layer.getSkipTextureUpdate(); + layer.setSkipTextureUpdate(true); + layer.performUpdates(gl, context); + + RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize())); + boolean isDirty = layer.isDirty(); + + if (isDirty) { + if (!RectF.intersects(layerBounds, context.viewport)) { + if (firstDirtyTile == null) + firstDirtyTile = layer; + dirtyTiles ++; + invalid = true; + } else { + // This tile intersects with the screen and is dirty, + // update it immediately. + layer.setSkipTextureUpdate(false); + screenUpdateDone = true; + layer.performUpdates(gl, context); + invalid = false; + } + } + + // We use the SkipTextureUpdate flag as a marker of a tile's + // validity. This is required, as sometimes layers are drawn + // without updating first, and we mustn't draw tiles that have + // been marked as invalid that we haven't updated. + layer.setSkipTextureUpdate(invalid); + } + + // Now if no tiles that intersect with the screen were updated, update + // a single tile that doesn't (if there are any). This has the effect + // of spreading out non-critical texture upload over time, and smoothing + // upload-related hitches. + if (!screenUpdateDone && firstDirtyTile != null) { + firstDirtyTile.setSkipTextureUpdate(false); + firstDirtyTile.performUpdates(gl, context); + dirtyTiles --; + } + + return (dirtyTiles == 0); + } + + private void refreshTileMetrics(Point origin, float resolution, boolean inTransaction) { + int x = 0, y = 0; + IntSize size = getSize(); + for (SingleTileLayer layer : mTiles) { + if (!inTransaction) + layer.beginTransaction(null); + + if (origin != null) + layer.setOrigin(new Point(origin.x + x, origin.y + y)); + if (resolution >= 0.0f) + layer.setResolution(resolution); + + if (!inTransaction) + layer.endTransaction(); + + x += mTileSize.width; + if (x >= size.width) { + x = 0; + y += mTileSize.height; + } + } + } + + @Override + public void setOrigin(Point newOrigin) { + super.setOrigin(newOrigin); + refreshTileMetrics(newOrigin, -1, true); + } + + @Override + public void setResolution(float newResolution) { + super.setResolution(newResolution); + refreshTileMetrics(null, newResolution, true); + } + + @Override + public void beginTransaction(LayerView aView) { + super.beginTransaction(aView); + + for (SingleTileLayer layer : mTiles) + layer.beginTransaction(aView); + } + + @Override + public void endTransaction() { + for (SingleTileLayer layer : mTiles) + layer.endTransaction(); + + super.endTransaction(); + } + + @Override + public void draw(RenderContext context) { + for (SingleTileLayer layer : mTiles) { + // We use the SkipTextureUpdate flag as a validity flag. If it's false, + // the contents of this tile are invalid and we shouldn't draw it. + if (layer.getSkipTextureUpdate()) + continue; + + // Avoid work, only draw tiles that intersect with the viewport + RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize())); + if (RectF.intersects(layerBounds, context.viewport)) + layer.draw(context); + } + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java new file mode 100644 index 000000000000..f139b202c7b5 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java @@ -0,0 +1,124 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.gfx.FloatSize; +import android.graphics.PointF; +import android.graphics.RectF; +import android.opengl.GLES11; +import android.opengl.GLES11Ext; +import android.util.Log; +import javax.microedition.khronos.opengles.GL10; +import java.nio.FloatBuffer; + +/** + * Encapsulates the logic needed to draw a nine-patch bitmap using OpenGL ES. + * + * For more information on nine-patch bitmaps, see the following document: + * http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch + */ +public class NinePatchTileLayer extends TileLayer { + private static final int PATCH_SIZE = 16; + private static final int TEXTURE_SIZE = 48; + + public NinePatchTileLayer(CairoImage image) { + super(false, image); + } + + @Override + public void draw(RenderContext context) { + if (!initialized()) + return; + + GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); + GLES11.glEnable(GL10.GL_BLEND); + try { + GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID()); + drawPatches(context); + } finally { + GLES11.glDisable(GL10.GL_BLEND); + } + } + + private void drawPatches(RenderContext context) { + /* + * We divide the nine-patch bitmap up as follows: + * + * +---+---+---+ + * | 0 | 1 | 2 | + * +---+---+---+ + * | 3 | | 4 | + * +---+---+---+ + * | 5 | 6 | 7 | + * +---+---+---+ + */ + + FloatSize size = context.pageSize; + float width = size.width, height = size.height; + + drawPatch(context, 0, 0, /* 0 */ + 0.0f, 0.0f, PATCH_SIZE, PATCH_SIZE); + drawPatch(context, PATCH_SIZE, 0, /* 1 */ + PATCH_SIZE, 0.0f, width, PATCH_SIZE); + drawPatch(context, PATCH_SIZE * 2, 0, /* 2 */ + PATCH_SIZE + width, 0.0f, PATCH_SIZE, PATCH_SIZE); + drawPatch(context, 0, PATCH_SIZE, /* 3 */ + 0.0f, PATCH_SIZE, PATCH_SIZE, height); + drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE, /* 4 */ + PATCH_SIZE + width, PATCH_SIZE, PATCH_SIZE, height); + drawPatch(context, 0, PATCH_SIZE * 2, /* 5 */ + 0.0f, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE); + drawPatch(context, PATCH_SIZE, PATCH_SIZE * 2, /* 6 */ + PATCH_SIZE, PATCH_SIZE + height, width, PATCH_SIZE); + drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE * 2, /* 7 */ + PATCH_SIZE + width, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE); + } + + private void drawPatch(RenderContext context, int textureX, int textureY, float tileX, + float tileY, float tileWidth, float tileHeight) { + int[] cropRect = { textureX, textureY + PATCH_SIZE, PATCH_SIZE, -PATCH_SIZE }; + GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect, + 0); + + RectF viewport = context.viewport; + float viewportHeight = viewport.height(); + float drawX = tileX - viewport.left - PATCH_SIZE; + float drawY = viewportHeight - (tileY + tileHeight - viewport.top - PATCH_SIZE); + GLES11Ext.glDrawTexfOES(drawX, drawY, 0.0f, tileWidth, tileHeight); + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/PanningPerfAPI.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/PanningPerfAPI.java new file mode 100644 index 000000000000..c9414ac8ecf0 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/PanningPerfAPI.java @@ -0,0 +1,125 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Kartikaya Gupta <kgupta@mozilla.com> + * Chris Lord <chrislord.net@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import java.util.ArrayList; +import java.util.List; +import android.os.SystemClock; +import android.util.Log; + +public class PanningPerfAPI { + private static final String LOGTAG = "GeckoPanningPerfAPI"; + + // make this large enough to avoid having to resize the frame time + // list, as that may be expensive and impact the thing we're trying + // to measure. + private static final int EXPECTED_FRAME_COUNT = 2048; + + private static boolean mRecordingFrames = false; + private static List<Long> mFrameTimes; + private static long mFrameStartTime; + + private static boolean mRecordingCheckerboard = false; + private static List<Float> mCheckerboardAmounts; + private static long mCheckerboardStartTime; + + public static void startFrameTimeRecording() { + if (mRecordingFrames) { + Log.e(LOGTAG, "Error: startFrameTimeRecording() called while already recording!"); + return; + } + mRecordingFrames = true; + if (mFrameTimes == null) { + mFrameTimes = new ArrayList<Long>(EXPECTED_FRAME_COUNT); + } else { + mFrameTimes.clear(); + } + mFrameStartTime = SystemClock.uptimeMillis(); + } + + public static List<Long> stopFrameTimeRecording() { + if (!mRecordingFrames) { + Log.e(LOGTAG, "Error: stopFrameTimeRecording() called when not recording!"); + return null; + } + mRecordingFrames = false; + return mFrameTimes; + } + + public static void recordFrameTime() { + // this will be called often, so try to make it as quick as possible + if (mRecordingFrames) { + mFrameTimes.add(SystemClock.uptimeMillis() - mFrameStartTime); + } + } + + public static boolean isRecordingCheckerboard() { + return mRecordingCheckerboard; + } + + public static void startCheckerboardRecording() { + if (mRecordingCheckerboard) { + Log.e(LOGTAG, "Error: startCheckerboardRecording() called while already recording!"); + return; + } + mRecordingCheckerboard = true; + if (mCheckerboardAmounts == null) { + mCheckerboardAmounts = new ArrayList<Float>(EXPECTED_FRAME_COUNT); + } else { + mCheckerboardAmounts.clear(); + } + mCheckerboardStartTime = SystemClock.uptimeMillis(); + } + + public static List<Float> stopCheckerboardRecording() { + if (!mRecordingCheckerboard) { + Log.e(LOGTAG, "Error: stopCheckerboardRecording() called when not recording!"); + return null; + } + mRecordingCheckerboard = false; + return mCheckerboardAmounts; + } + + public static void recordCheckerboard(float amount) { + // this will be called often, so try to make it as quick as possible + if (mRecordingCheckerboard) { + mCheckerboardAmounts.add(amount); + } + } +}
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/PointUtils.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/PointUtils.java new file mode 100644 index 000000000000..bff9f9f345a1 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/PointUtils.java @@ -0,0 +1,96 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Kartikaya Gupta <kgupta@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import android.graphics.Point; +import android.graphics.PointF; +import android.util.FloatMath; + +import org.json.JSONObject; +import org.json.JSONException; +import org.mozilla.gecko.util.FloatUtils; + +import java.lang.Math; + +public final class PointUtils { + public static PointF add(PointF one, PointF two) { + return new PointF(one.x + two.x, one.y + two.y); + } + + public static PointF subtract(PointF one, PointF two) { + return new PointF(one.x - two.x, one.y - two.y); + } + + public static PointF scale(PointF point, float factor) { + return new PointF(point.x * factor, point.y * factor); + } + + public static Point round(PointF point) { + return new Point(Math.round(point.x), Math.round(point.y)); + } + + /* Returns a new point that is a linear interpolation between start and end points. weight conrols the weighting + * of each of the original points (weight = 1 returns endPoint, weight = 0 returns startPoint) + */ + public static PointF interpolate(PointF startPoint, PointF endPoint, float weight) { + float x = FloatUtils.interpolate(startPoint.x, endPoint.x, weight); + float y = FloatUtils.interpolate(startPoint.y, endPoint.y, weight); + return new PointF(x, y); + } + + /* Computes the magnitude of the given vector. */ + public static float distance(PointF point) { + return (float)Math.sqrt(point.x * point.x + point.y * point.y); + } + + /** Computes the scalar distance between two points. */ + public static float distance(PointF one, PointF two) { + return PointF.length(one.x - two.x, one.y - two.y); + } + + public static JSONObject toJSON(PointF point) throws JSONException { + // Ensure we put ints, not longs, because Gecko message handlers call getInt(). + int x = Math.round(point.x); + int y = Math.round(point.y); + JSONObject json = new JSONObject(); + json.put("x", x); + json.put("y", y); + return json; + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/RectUtils.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/RectUtils.java new file mode 100644 index 000000000000..ffc87b3dcc2a --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/RectUtils.java @@ -0,0 +1,128 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Kartikaya Gupta <kgupta@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.util.FloatUtils; +import android.graphics.Rect; +import android.graphics.RectF; +import org.json.JSONException; +import org.json.JSONObject; + +public final class RectUtils { + public static Rect create(JSONObject json) { + try { + int x = json.getInt("x"); + int y = json.getInt("y"); + int width = json.getInt("width"); + int height = json.getInt("height"); + return new Rect(x, y, x + width, y + height); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public static Rect contract(Rect rect, int lessWidth, int lessHeight) { + float halfLessWidth = lessWidth / 2.0f; + float halfLessHeight = lessHeight / 2.0f; + return new Rect(Math.round(rect.left + halfLessWidth), + Math.round(rect.top + halfLessHeight), + Math.round(rect.right - halfLessWidth), + Math.round(rect.bottom - halfLessHeight)); + } + + public static RectF contract(RectF rect, float lessWidth, float lessHeight) { + float halfLessWidth = lessWidth / 2; + float halfLessHeight = lessHeight / 2; + return new RectF(rect.left + halfLessWidth, + rect.top + halfLessHeight, + rect.right - halfLessWidth, + rect.bottom - halfLessHeight); + } + + public static RectF expand(RectF rect, float moreWidth, float moreHeight) { + float halfMoreWidth = moreWidth / 2; + float halfMoreHeight = moreHeight / 2; + return new RectF(rect.left - halfMoreWidth, + rect.top - halfMoreHeight, + rect.right + halfMoreWidth, + rect.bottom + halfMoreHeight); + } + + public static RectF intersect(RectF one, RectF two) { + float left = Math.max(one.left, two.left); + float top = Math.max(one.top, two.top); + float right = Math.min(one.right, two.right); + float bottom = Math.min(one.bottom, two.bottom); + return new RectF(left, top, Math.max(right, left), Math.max(bottom, top)); + } + + public static RectF scale(RectF rect, float scale) { + float x = rect.left * scale; + float y = rect.top * scale; + return new RectF(x, y, + x + (rect.width() * scale), + y + (rect.height() * scale)); + } + + public static Rect round(RectF rect) { + return new Rect(Math.round(rect.left), Math.round(rect.top), + Math.round(rect.right), Math.round(rect.bottom)); + } + + public static IntSize getSize(Rect rect) { + return new IntSize(rect.width(), rect.height()); + } + + /* + * Returns the rect that represents a linear transition between `from` and `to` at time `t`, + * which is on the scale [0, 1). + */ + public static RectF interpolate(RectF from, RectF to, float t) { + return new RectF(FloatUtils.interpolate(from.left, to.left, t), + FloatUtils.interpolate(from.top, to.top, t), + FloatUtils.interpolate(from.right, to.right, t), + FloatUtils.interpolate(from.bottom, to.bottom, t)); + } + + public static boolean fuzzyEquals(RectF a, RectF b) { + return FloatUtils.fuzzyEquals(a.top, b.top) + && FloatUtils.fuzzyEquals(a.left, b.left) + && FloatUtils.fuzzyEquals(a.right, b.right) + && FloatUtils.fuzzyEquals(a.bottom, b.bottom); + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/ScrollbarLayer.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/ScrollbarLayer.java new file mode 100644 index 000000000000..e01e1cf369c0 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/ScrollbarLayer.java @@ -0,0 +1,229 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Kartikaya Gupta <kgupta@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.RectF; +import android.opengl.GLES11; +import android.opengl.GLES11Ext; +import android.util.Log; +import java.nio.ByteBuffer; +import javax.microedition.khronos.opengles.GL10; + +import org.libreoffice.LOKitShell; +import org.mozilla.gecko.util.FloatUtils; +//import org.mozilla.gecko.GeckoAppShell; + +/** + * Draws a small rect. This is scaled to become a scrollbar. + */ +public class ScrollbarLayer extends TileLayer { + public static final long FADE_DELAY = 500; // milliseconds before fade-out starts + private static final float FADE_AMOUNT = 0.03f; // how much (as a percent) the scrollbar should fade per frame + + private static final int PADDING = 1; // gap between scrollbar and edge of viewport + private static final int BAR_SIZE = 12; + private static final int CAP_RADIUS = (BAR_SIZE / 2); + + private static final int[] CROPRECT_MIDPIXEL = new int[] { CAP_RADIUS, CAP_RADIUS, 1, 1 }; + private static final int[] CROPRECT_TOPCAP = new int[] { 0, CAP_RADIUS, BAR_SIZE, -CAP_RADIUS }; + private static final int[] CROPRECT_BOTTOMCAP = new int[] { 0, BAR_SIZE, BAR_SIZE, -CAP_RADIUS }; + private static final int[] CROPRECT_LEFTCAP = new int[] { 0, BAR_SIZE, CAP_RADIUS, -BAR_SIZE }; + private static final int[] CROPRECT_RIGHTCAP = new int[] { CAP_RADIUS, BAR_SIZE, CAP_RADIUS, -BAR_SIZE }; + + private final boolean mVertical; + private final ByteBuffer mBuffer; + private final Bitmap mBitmap; + private final Canvas mCanvas; + private float mOpacity; + private boolean mFinalized = false; + + private ScrollbarLayer(CairoImage image, boolean vertical, ByteBuffer buffer) { + super(false, image); + mVertical = vertical; + mBuffer = buffer; + + IntSize size = image.getSize(); + mBitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBitmap); + } + + protected void finalize() throws Throwable { + try { + if (!mFinalized && mBuffer != null) + /*GeckoAppShell*/ LOKitShell.freeDirectBuffer(mBuffer); + mFinalized = true; + } finally { + super.finalize(); + } + } + + + public static ScrollbarLayer create(boolean vertical) { + // just create an empty image for now, it will get drawn + // on demand anyway + int imageSize = IntSize.nextPowerOfTwo(BAR_SIZE); + ByteBuffer buffer = /*GeckoAppShell*/LOKitShell.allocateDirectBuffer(imageSize * imageSize * 4); + CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize, CairoImage.FORMAT_ARGB32); + return new ScrollbarLayer(image, vertical, buffer); + } + + /** + * Decrease the opacity of the scrollbar by one frame's worth. + * Return true if the opacity was decreased, or false if the scrollbars + * are already fully faded out. + */ + public boolean fade() { + if (FloatUtils.fuzzyEquals(mOpacity, 0.0f)) { + return false; + } + beginTransaction(); + try { + setOpacity(Math.max(mOpacity - FADE_AMOUNT, 0.0f)); + invalidate(); + } finally { + endTransaction(); + } + return true; + } + + /** + * Restore the opacity of the scrollbar to fully opaque. + * Return true if the opacity was changed, or false if the scrollbars + * are already fully opaque. + */ + public boolean unfade() { + if (FloatUtils.fuzzyEquals(mOpacity, 1.0f)) { + return false; + } + beginTransaction(); + try { + setOpacity(1.0f); + invalidate(); + } finally { + endTransaction(); + } + return true; + } + + private void setOpacity(float opacity) { + mOpacity = opacity; + + Paint foregroundPaint = new Paint(); + foregroundPaint.setAntiAlias(true); + foregroundPaint.setStyle(Paint.Style.FILL); + // use a (a,r,g,b) color of (127,0,0,0), and multiply the alpha by mOpacity for fading + foregroundPaint.setColor(Color.argb(Math.round(mOpacity * 127), 0, 0, 0)); + + mCanvas.drawColor(Color.argb(0, 0, 0, 0), PorterDuff.Mode.CLEAR); + mCanvas.drawCircle(CAP_RADIUS, CAP_RADIUS, CAP_RADIUS, foregroundPaint); + + mBitmap.copyPixelsToBuffer(mBuffer.asIntBuffer()); + } + + @Override + public void draw(RenderContext context) { + if (!initialized()) + return; + + try { + GLES11.glEnable(GL10.GL_BLEND); + GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); + + Rect rect = RectUtils.round(mVertical ? getVerticalRect(context) : getHorizontalRect(context)); + GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID()); + + float viewHeight = context.viewport.height(); + + // for the body of the scrollbar, we take a 1x1 pixel from the center of the image + // and scale it to become the bar + GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_MIDPIXEL, 0); + GLES11Ext.glDrawTexfOES(rect.left, viewHeight - rect.bottom, 0.0f, rect.width(), rect.height()); + + if (mVertical) { + // top endcap + GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_TOPCAP, 0); + GLES11Ext.glDrawTexfOES(rect.left, viewHeight - rect.top, 0.0f, BAR_SIZE, CAP_RADIUS); + // bottom endcap + GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_BOTTOMCAP, 0); + GLES11Ext.glDrawTexfOES(rect.left, viewHeight - (rect.bottom + CAP_RADIUS), 0.0f, BAR_SIZE, CAP_RADIUS); + } else { + // left endcap + GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_LEFTCAP, 0); + GLES11Ext.glDrawTexfOES(rect.left - CAP_RADIUS, viewHeight - rect.bottom, 0.0f, CAP_RADIUS, BAR_SIZE); + // right endcap + GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_RIGHTCAP, 0); + GLES11Ext.glDrawTexfOES(rect.right, viewHeight - rect.bottom, 0.0f, CAP_RADIUS, BAR_SIZE); + } + } finally { + GLES11.glDisable(GL10.GL_BLEND); + } + } + + private RectF getVerticalRect(RenderContext context) { + RectF viewport = context.viewport; + FloatSize pageSize = context.pageSize; + float barStart = (viewport.height() * viewport.top / pageSize.height) + CAP_RADIUS; + float barEnd = (viewport.height() * viewport.bottom / pageSize.height) - CAP_RADIUS; + if (barStart > barEnd) { + float middle = (barStart + barEnd) / 2.0f; + barStart = barEnd = middle; + } + float right = viewport.width() - PADDING; + return new RectF(right - BAR_SIZE, barStart, right, barEnd); + } + + private RectF getHorizontalRect(RenderContext context) { + RectF viewport = context.viewport; + FloatSize pageSize = context.pageSize; + float barStart = (viewport.width() * viewport.left / pageSize.width) + CAP_RADIUS; + float barEnd = (viewport.width() * viewport.right / pageSize.width) - CAP_RADIUS; + if (barStart > barEnd) { + float middle = (barStart + barEnd) / 2.0f; + barStart = barEnd = middle; + } + float bottom = viewport.height() - PADDING; + return new RectF(barStart, bottom - BAR_SIZE, barEnd, bottom); + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/SingleTileLayer.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/SingleTileLayer.java new file mode 100644 index 000000000000..8993b94d5ee1 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/SingleTileLayer.java @@ -0,0 +1,100 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.gfx.CairoImage; +import org.mozilla.gecko.gfx.IntSize; +import org.mozilla.gecko.gfx.LayerController; +import org.mozilla.gecko.gfx.TileLayer; +import android.graphics.PointF; +import android.graphics.RectF; +import android.opengl.GLES11; +import android.opengl.GLES11Ext; +import android.util.Log; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import javax.microedition.khronos.opengles.GL10; + +/** + * Encapsulates the logic needed to draw a single textured tile. + * + * TODO: Repeating textures really should be their own type of layer. + */ +public class SingleTileLayer extends TileLayer { + public SingleTileLayer(CairoImage image) { this(false, image); } + + public SingleTileLayer(boolean repeat, CairoImage image) { + super(repeat, image); + } + + @Override + public void draw(RenderContext context) { + // mTextureIDs may be null here during startup if Layer.java's draw method + // failed to acquire the transaction lock and call performUpdates. + if (!initialized()) + return; + + GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID()); + + RectF bounds; + int[] cropRect; + IntSize size = getSize(); + RectF viewport = context.viewport; + + if (repeats()) { + bounds = new RectF(0.0f, 0.0f, viewport.width(), viewport.height()); + int width = Math.round(viewport.width()); + int height = Math.round(-viewport.height()); + cropRect = new int[] { 0, size.height, width, height }; + } else { + bounds = getBounds(context, new FloatSize(size)); + cropRect = new int[] { 0, size.height, size.width, -size.height }; + } + + GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect, + 0); + + float height = bounds.height(); + float left = bounds.left - viewport.left; + float top = viewport.height() - (bounds.top + height - viewport.top); + + GLES11Ext.glDrawTexfOES(left, top, 0.0f, bounds.width(), height); + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TextLayer.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TextLayer.java new file mode 100644 index 000000000000..f2cc640baaa7 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TextLayer.java @@ -0,0 +1,117 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +//import org.mozilla.gecko.GeckoAppShell; +import org.libreoffice.LOKitShell; +import org.mozilla.gecko.gfx.BufferedCairoImage; +import org.mozilla.gecko.gfx.CairoImage; +import org.mozilla.gecko.gfx.IntSize; +import org.mozilla.gecko.gfx.SingleTileLayer; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.util.Log; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +/** + * Draws text on a layer. This is used for the frame rate meter. + */ +public class TextLayer extends SingleTileLayer { + private final ByteBuffer mBuffer; + private final IntSize mSize; + private boolean mFinalized = false; + + /* + * This awkward pattern is necessary due to Java's restrictions on when one can call superclass + * constructors. + */ + private TextLayer(ByteBuffer buffer, BufferedCairoImage image, IntSize size, String text) { + super(false, image); + mBuffer = buffer; + mSize = size; + renderText(text); + } + + protected void finalize() throws Throwable { + try { + if (!mFinalized && mBuffer != null) + /*GeckoAppShell*/ LOKitShell.freeDirectBuffer(mBuffer); + mFinalized = true; + } finally { + super.finalize(); + } + } + + public static TextLayer create(IntSize size, String text) { + ByteBuffer buffer = /*GeckoAppShell*/LOKitShell.allocateDirectBuffer(size.width * size.height * 4); + BufferedCairoImage image = new BufferedCairoImage(buffer, size.width, size.height, + CairoImage.FORMAT_ARGB32); + return new TextLayer(buffer, image, size, text); + } + + public void setText(String text) { + renderText(text); + invalidate(); + } + + private void renderText(String text) { + Bitmap bitmap = Bitmap.createBitmap(mSize.width, mSize.height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + Paint textPaint = new Paint(); + textPaint.setAntiAlias(true); + textPaint.setColor(Color.WHITE); + textPaint.setFakeBoldText(true); + textPaint.setTextSize(18.0f); + textPaint.setTypeface(Typeface.DEFAULT_BOLD); + float width = textPaint.measureText(text) + 18.0f; + + Paint backgroundPaint = new Paint(); + backgroundPaint.setColor(Color.argb(127, 0, 0, 0)); + canvas.drawRect(0.0f, 0.0f, width, 18.0f + 6.0f, backgroundPaint); + + canvas.drawText(text, 6.0f, 18.0f, textPaint); + + bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer()); + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TextureGenerator.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TextureGenerator.java new file mode 100644 index 000000000000..4392c55e1ae7 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TextureGenerator.java @@ -0,0 +1,73 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * James Willcox <jwillcox@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import android.opengl.GLES10; +import java.util.Stack; + +public class TextureGenerator { + private static final int MIN_TEXTURES = 5; + + private static TextureGenerator sSharedInstance; + private Stack<Integer> mTextureIds; + + private TextureGenerator() { mTextureIds = new Stack<Integer>(); } + + public static TextureGenerator get() { + if (sSharedInstance == null) + sSharedInstance = new TextureGenerator(); + return sSharedInstance; + } + + public synchronized int take() { + if (mTextureIds.empty()) + return 0; + + return (int)mTextureIds.pop(); + } + + public synchronized void fill() { + int[] textures = new int[1]; + while (mTextureIds.size() < MIN_TEXTURES) { + GLES10.glGenTextures(1, textures, 0); + mTextureIds.push(textures[0]); + } + } +} + + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TextureReaper.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TextureReaper.java new file mode 100644 index 000000000000..047406234b44 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TextureReaper.java @@ -0,0 +1,75 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import javax.microedition.khronos.opengles.GL10; +import java.util.ArrayList; + +/** Manages a list of dead tiles, so we don't leak resources. */ +public class TextureReaper { + private static TextureReaper sSharedInstance; + private ArrayList<Integer> mDeadTextureIDs; + + private TextureReaper() { mDeadTextureIDs = new ArrayList<Integer>(); } + + public static TextureReaper get() { + if (sSharedInstance == null) + sSharedInstance = new TextureReaper(); + return sSharedInstance; + } + + public void add(int[] textureIDs) { + for (int textureID : textureIDs) + add(textureID); + } + + public void add(int textureID) { + mDeadTextureIDs.add(textureID); + } + + public void reap(GL10 gl) { + int[] deadTextureIDs = new int[mDeadTextureIDs.size()]; + for (int i = 0; i < deadTextureIDs.length; i++) + deadTextureIDs[i] = mDeadTextureIDs.get(i); + mDeadTextureIDs.clear(); + + gl.glDeleteTextures(deadTextureIDs.length, deadTextureIDs, 0); + } +} + + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TileLayer.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TileLayer.java new file mode 100644 index 000000000000..ad52229cfbcf --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TileLayer.java @@ -0,0 +1,250 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import android.graphics.Rect; +import android.graphics.RectF; +import android.opengl.GLES20; +import android.util.Log; +import javax.microedition.khronos.opengles.GL10; +import javax.microedition.khronos.opengles.GL11Ext; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + * Base class for tile layers, which encapsulate the logic needed to draw textured tiles in OpenGL + * ES. + */ +public abstract class TileLayer extends Layer { + private static final String LOGTAG = "GeckoTileLayer"; + + private final Rect mDirtyRect; + private final CairoImage mImage; + private final boolean mRepeat; + private IntSize mSize; + private boolean mSkipTextureUpdate; + private int[] mTextureIDs; + + public TileLayer(boolean repeat, CairoImage image) { + mRepeat = repeat; + mImage = image; + mSize = new IntSize(0, 0); + mSkipTextureUpdate = false; + + IntSize bufferSize = mImage.getSize(); + mDirtyRect = new Rect(); + } + + @Override + public IntSize getSize() { return mImage.getSize(); } + + protected boolean repeats() { return mRepeat; } + protected int getTextureID() { return mTextureIDs[0]; } + protected boolean initialized() { return mImage != null && mTextureIDs != null; } + + @Override + protected void finalize() throws Throwable { + if (mTextureIDs != null) + TextureReaper.get().add(mTextureIDs); + } + + /** + * Invalidates the given rect so that it will be uploaded again. Only valid inside a + * transaction. + */ + public void invalidate(Rect rect) { + if (!inTransaction()) + throw new RuntimeException("invalidate() is only valid inside a transaction"); + mDirtyRect.union(rect); + } + + public void invalidate() { + IntSize bufferSize = mImage.getSize(); + invalidate(new Rect(0, 0, bufferSize.width, bufferSize.height)); + } + + public boolean isDirty() { + return mImage.getSize().isPositive() && (mTextureIDs == null || !mDirtyRect.isEmpty()); + } + + private void validateTexture(GL10 gl) { + /* Calculate the ideal texture size. This must be a power of two if + * the texture is repeated or OpenGL ES 2.0 isn't supported, as + * OpenGL ES 2.0 is required for NPOT texture support (without + * extensions), but doesn't support repeating NPOT textures. + * + * XXX Currently, we don't pick a GLES 2.0 context, so always round. + */ + IntSize bufferSize = mImage.getSize(); + IntSize textureSize = bufferSize; + + textureSize = bufferSize.nextPowerOfTwo(); + + if (!textureSize.equals(mSize)) { + mSize = textureSize; + + // Delete the old texture + if (mTextureIDs != null) { + TextureReaper.get().add(mTextureIDs); + mTextureIDs = null; + + // Free the texture immediately, so we don't incur a + // temporarily increased memory usage. + TextureReaper.get().reap(gl); + } + } + } + + /** Tells the tile not to update the texture on the next update. */ + public void setSkipTextureUpdate(boolean skip) { + mSkipTextureUpdate = skip; + } + + public boolean getSkipTextureUpdate() { + return mSkipTextureUpdate; + } + + @Override + protected boolean performUpdates(GL10 gl, RenderContext context) { + super.performUpdates(gl, context); + + if (mSkipTextureUpdate) + return false; + + // Reallocate the texture if the size has changed + validateTexture(gl); + + // Don't do any work if the image has an invalid size. + if (!mImage.getSize().isPositive()) + return true; + + // If we haven't allocated a texture, assume the whole region is dirty + if (mTextureIDs == null) + uploadFullTexture(gl); + else + uploadDirtyRect(gl, mDirtyRect); + + mDirtyRect.setEmpty(); + + return true; + } + + private void uploadFullTexture(GL10 gl) { + IntSize bufferSize = mImage.getSize(); + uploadDirtyRect(gl, new Rect(0, 0, bufferSize.width, bufferSize.height)); + } + + private void uploadDirtyRect(GL10 gl, Rect dirtyRect) { + // If we have nothing to upload, just return for now + if (dirtyRect.isEmpty()) + return; + + // It's possible that the buffer will be null, check for that and return + ByteBuffer imageBuffer = mImage.getBuffer(); + if (imageBuffer == null) + return; + + boolean newlyCreated = false; + + if (mTextureIDs == null) { + mTextureIDs = new int[1]; + gl.glGenTextures(mTextureIDs.length, mTextureIDs, 0); + newlyCreated = true; + } + + IntSize bufferSize = mImage.getSize(); + Rect bufferRect = new Rect(0, 0, bufferSize.width, bufferSize.height); + + int cairoFormat = mImage.getFormat(); + CairoGLInfo glInfo = new CairoGLInfo(cairoFormat); + + bindAndSetGLParameters(gl); + + if (newlyCreated || dirtyRect.contains(bufferRect)) { + if (mSize.equals(bufferSize)) { + gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height, + 0, glInfo.format, glInfo.type, imageBuffer); + return; + } else { + gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height, + 0, glInfo.format, glInfo.type, null); + gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0, bufferSize.width, bufferSize.height, + glInfo.format, glInfo.type, imageBuffer); + return; + } + } + + // Make sure that the dirty region intersects with the buffer rect, + // otherwise we'll end up with an invalid buffer pointer. + if (!Rect.intersects(dirtyRect, bufferRect)) + return; + + /* + * Upload the changed rect. We have to widen to the full width of the texture + * because we can't count on the device having support for GL_EXT_unpack_subimage, + * and going line-by-line is too slow. + * + * XXX We should still use GL_EXT_unpack_subimage when available. + */ + Buffer viewBuffer = imageBuffer.slice(); + int bpp = CairoUtils.bitsPerPixelForCairoFormat(cairoFormat) / 8; + int position = dirtyRect.top * bufferSize.width * bpp; + if (position > viewBuffer.limit()) { + Log.e(LOGTAG, "### Position outside tile! " + dirtyRect.top); + return; + } + + viewBuffer.position(position); + gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width, + Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()), + glInfo.format, glInfo.type, viewBuffer); + } + + private void bindAndSetGLParameters(GL10 gl) { + gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]); + gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); + gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); + + int repeatMode = mRepeat ? GL10.GL_REPEAT : GL10.GL_CLAMP_TO_EDGE; + gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, repeatMode); + gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, repeatMode); + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/ViewportMetrics.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/ViewportMetrics.java new file mode 100644 index 000000000000..d03b423df9bb --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/ViewportMetrics.java @@ -0,0 +1,322 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * Chris Lord <chrislord.net@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.DisplayMetrics; + +import org.libreoffice.LibreOfficeMainActivity; +import org.mozilla.gecko.util.FloatUtils; +//import org.mozilla.gecko.GeckoApp; +import org.mozilla.gecko.gfx.FloatSize; +import org.mozilla.gecko.gfx.IntSize; +import org.mozilla.gecko.gfx.LayerController; +import org.mozilla.gecko.gfx.RectUtils; +import org.json.JSONException; +import org.json.JSONObject; +import android.util.Log; + +/** + * ViewportMetrics manages state and contains some utility functions related to + * the page viewport for the Gecko layer client to use. + */ +public class ViewportMetrics { + private static final String LOGTAG = "GeckoViewportMetrics"; + + private FloatSize mPageSize; + private RectF mViewportRect; + private PointF mViewportOffset; + private float mZoomFactor; + private boolean mAllowZoom; + + // A scale from -1,-1 to 1,1 that represents what edge of the displayport + // we want the viewport to be biased towards. + private PointF mViewportBias; + private static final float MAX_BIAS = 0.8f; + + public ViewportMetrics() { + DisplayMetrics metrics = new DisplayMetrics(); + /*GeckoApp*/LibreOfficeMainActivity.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics); + + mPageSize = new FloatSize(metrics.widthPixels, metrics.heightPixels); + mViewportRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels); + mViewportOffset = new PointF(0, 0); + mZoomFactor = 1.0f; + mViewportBias = new PointF(0.0f, 0.0f); + mAllowZoom = true; + } + + public ViewportMetrics(ViewportMetrics viewport) { + mPageSize = new FloatSize(viewport.getPageSize()); + mViewportRect = new RectF(viewport.getViewport()); + PointF offset = viewport.getViewportOffset(); + mViewportOffset = new PointF(offset.x, offset.y); + mZoomFactor = viewport.getZoomFactor(); + mViewportBias = viewport.mViewportBias; + mAllowZoom = viewport.mAllowZoom; + } + + public ViewportMetrics(JSONObject json) throws JSONException { + float x = (float)json.getDouble("x"); + float y = (float)json.getDouble("y"); + float width = (float)json.getDouble("width"); + float height = (float)json.getDouble("height"); + float pageWidth = (float)json.getDouble("pageWidth"); + float pageHeight = (float)json.getDouble("pageHeight"); + float offsetX = (float)json.getDouble("offsetX"); + float offsetY = (float)json.getDouble("offsetY"); + float zoom = (float)json.getDouble("zoom"); + + mAllowZoom = json.getBoolean("allowZoom"); + + mPageSize = new FloatSize(pageWidth, pageHeight); + mViewportRect = new RectF(x, y, x + width, y + height); + mViewportOffset = new PointF(offsetX, offsetY); + mZoomFactor = zoom; + mViewportBias = new PointF(0.0f, 0.0f); + } + + public PointF getOptimumViewportOffset(IntSize displayportSize) { + /* XXX Until bug #524925 is fixed, changing the viewport origin will + * cause unnecessary relayouts. This may cause rendering time to + * increase and should be considered. + */ + RectF viewport = getClampedViewport(); + + FloatSize bufferSpace = new FloatSize(displayportSize.width - viewport.width(), + displayportSize.height - viewport.height()); + PointF optimumOffset = + new PointF(bufferSpace.width * ((mViewportBias.x + 1.0f) / 2.0f), + bufferSpace.height * ((mViewportBias.y + 1.0f) / 2.0f)); + + // Make sure this offset won't cause wasted pixels in the displayport + // (i.e. make sure the resultant displayport intersects with the page + // as much as possible) + if (viewport.left - optimumOffset.x < 0) + optimumOffset.x = viewport.left; + else if ((bufferSpace.width - optimumOffset.x) + viewport.right > mPageSize.width) + optimumOffset.x = bufferSpace.width - (mPageSize.width - viewport.right); + + if (viewport.top - optimumOffset.y < 0) + optimumOffset.y = viewport.top; + else if ((bufferSpace.height - optimumOffset.y) + viewport.bottom > mPageSize.height) + optimumOffset.y = bufferSpace.height - (mPageSize.height - viewport.bottom); + + return new PointF(Math.round(optimumOffset.x), Math.round(optimumOffset.y)); + } + + public PointF getOrigin() { + return new PointF(mViewportRect.left, mViewportRect.top); + } + + public PointF getDisplayportOrigin() { + return new PointF(mViewportRect.left - mViewportOffset.x, + mViewportRect.top - mViewportOffset.y); + } + + public FloatSize getSize() { + return new FloatSize(mViewportRect.width(), mViewportRect.height()); + } + + public RectF getViewport() { + return mViewportRect; + } + + /** Returns the viewport rectangle, clamped within the page-size. */ + public RectF getClampedViewport() { + RectF clampedViewport = new RectF(mViewportRect); + + // While the viewport size ought to never exceed the page size, we + // do the clamping in this order to make sure that the origin is + // never negative. + if (clampedViewport.right > mPageSize.width) + clampedViewport.offset(mPageSize.width - clampedViewport.right, 0); + if (clampedViewport.left < 0) + clampedViewport.offset(-clampedViewport.left, 0); + + if (clampedViewport.bottom > mPageSize.height) + clampedViewport.offset(0, mPageSize.height - clampedViewport.bottom); + if (clampedViewport.top < 0) + clampedViewport.offset(0, -clampedViewport.top); + + return clampedViewport; + } + + public PointF getViewportOffset() { + return mViewportOffset; + } + + public FloatSize getPageSize() { + return mPageSize; + } + + public float getZoomFactor() { + return mZoomFactor; + } + + public boolean getAllowZoom() { + return mAllowZoom; + } + + public void setPageSize(FloatSize pageSize) { + mPageSize = pageSize; + } + + public void setViewport(RectF viewport) { + mViewportRect = viewport; + } + + public void setOrigin(PointF origin) { + // When the origin is set, we compare it with the last value set and + // change the viewport bias accordingly, so that any viewport based + // on these metrics will have a larger buffer in the direction of + // movement. + + // XXX Note the comment about bug #524925 in getOptimumViewportOffset. + // Ideally, the viewport bias would be a sliding scale, but we + // don't want to change it too often at the moment. + if (FloatUtils.fuzzyEquals(origin.x, mViewportRect.left)) + mViewportBias.x = 0; + else + mViewportBias.x = ((mViewportRect.left - origin.x) > 0) ? MAX_BIAS : -MAX_BIAS; + if (FloatUtils.fuzzyEquals(origin.y, mViewportRect.top)) + mViewportBias.y = 0; + else + mViewportBias.y = ((mViewportRect.top - origin.y) > 0) ? MAX_BIAS : -MAX_BIAS; + + mViewportRect.set(origin.x, origin.y, + origin.x + mViewportRect.width(), + origin.y + mViewportRect.height()); + } + + public void setSize(FloatSize size) { + mViewportRect.right = mViewportRect.left + size.width; + mViewportRect.bottom = mViewportRect.top + size.height; + } + + public void setViewportOffset(PointF offset) { + mViewportOffset = offset; + } + + public void setZoomFactor(float zoomFactor) { + mZoomFactor = zoomFactor; + } + + /* This will set the zoom factor and re-scale page-size and viewport offset + * accordingly. The given focus will remain at the same point on the screen + * after scaling. + */ + public void scaleTo(float newZoomFactor, PointF focus) { + float scaleFactor = newZoomFactor / mZoomFactor; + + mPageSize = mPageSize.scale(scaleFactor); + + PointF origin = getOrigin(); + origin.offset(focus.x, focus.y); + origin = PointUtils.scale(origin, scaleFactor); + origin.offset(-focus.x, -focus.y); + setOrigin(origin); + + mZoomFactor = newZoomFactor; + + // Similar to setOrigin, set the viewport bias based on the focal point + // of the zoom so that a viewport based on these metrics will have a + // larger buffer based on the direction of movement when scaling. + // + // This is biased towards scaling outwards, as zooming in doesn't + // really require a viewport bias. + mViewportBias.set(((focus.x / mViewportRect.width()) * (2.0f * MAX_BIAS)) - MAX_BIAS, + ((focus.y / mViewportRect.height()) * (2.0f * MAX_BIAS)) - MAX_BIAS); + } + + /* + * Returns the viewport metrics that represent a linear transition between `from` and `to` at + * time `t`, which is on the scale [0, 1). This function interpolates the viewport rect, the + * page size, the offset, and the zoom factor. + */ + public ViewportMetrics interpolate(ViewportMetrics to, float t) { + ViewportMetrics result = new ViewportMetrics(); + result.mPageSize = mPageSize.interpolate(to.mPageSize, t); + result.mZoomFactor = FloatUtils.interpolate(mZoomFactor, to.mZoomFactor, t); + result.mViewportRect = RectUtils.interpolate(mViewportRect, to.mViewportRect, t); + result.mViewportOffset = PointUtils.interpolate(mViewportOffset, to.mViewportOffset, t); + return result; + } + + public boolean fuzzyEquals(ViewportMetrics other) { + return mPageSize.fuzzyEquals(other.mPageSize) + && RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect) + && FloatUtils.fuzzyEquals(mViewportOffset, other.mViewportOffset) + && FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor); + } + + public String toJSON() { + // Round off height and width. Since the height and width are the size of the screen, it + // makes no sense to send non-integer coordinates to Gecko. + int height = Math.round(mViewportRect.height()); + int width = Math.round(mViewportRect.width()); + + StringBuffer sb = new StringBuffer(256); + sb.append("{ \"x\" : ").append(mViewportRect.left) + .append(", \"y\" : ").append(mViewportRect.top) + .append(", \"width\" : ").append(width) + .append(", \"height\" : ").append(height) + .append(", \"pageWidth\" : ").append(mPageSize.width) + .append(", \"pageHeight\" : ").append(mPageSize.height) + .append(", \"offsetX\" : ").append(mViewportOffset.x) + .append(", \"offsetY\" : ").append(mViewportOffset.y) + .append(", \"zoom\" : ").append(mZoomFactor) + .append(" }"); + return sb.toString(); + } + + @Override + public String toString() { + StringBuffer buff = new StringBuffer(128); + buff.append("v=").append(mViewportRect.toString()) + .append(" p=").append(mPageSize.toString()) + .append(" z=").append(mZoomFactor) + .append(" o=").append(mViewportOffset.x) + .append(',').append(mViewportOffset.y); + return buff.toString(); + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/WidgetTileLayer.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/WidgetTileLayer.java new file mode 100644 index 000000000000..7cbcdb3a016d --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/WidgetTileLayer.java @@ -0,0 +1,129 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * James Willcox <jwillcox@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.gfx; + +import org.libreoffice.LOKitShell; +import org.mozilla.gecko.gfx.LayerController; +import org.mozilla.gecko.gfx.SingleTileLayer; +//import org.mozilla.gecko.GeckoAppShell; +import android.opengl.GLES11; +import android.opengl.GLES11Ext; +import android.graphics.RectF; +import android.util.Log; +import javax.microedition.khronos.opengles.GL10; + +/** + * Encapsulates the logic needed to draw the single-tiled Gecko texture + */ +public class WidgetTileLayer extends Layer { + private static final String LOGTAG = "WidgetTileLayer"; + + private int[] mTextureIDs; + private CairoImage mImage; + + public WidgetTileLayer(CairoImage image) { + mImage = image; + } + + protected boolean initialized() { return mTextureIDs != null; } + + @Override + public IntSize getSize() { return mImage.getSize(); } + + protected void bindAndSetGLParameters() { + GLES11.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]); + GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); + GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); + } + + @Override + protected void finalize() throws Throwable { + if (mTextureIDs != null) + TextureReaper.get().add(mTextureIDs); + } + + @Override + protected boolean performUpdates(GL10 gl, RenderContext context) { + super.performUpdates(gl, context); + + if (mTextureIDs == null) { + mTextureIDs = new int[1]; + GLES11.glGenTextures(1, mTextureIDs, 0); + } + + bindAndSetGLParameters(); + /*GeckoAppShell*/LOKitShell.bindWidgetTexture(); + + return true; + } + + @Override + public void draw(RenderContext context) { + // mTextureIDs may be null here during startup if Layer.java's draw method + // failed to acquire the transaction lock and call performUpdates. + if (!initialized()) + return; + + GLES11.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]); + + RectF bounds; + int[] cropRect; + IntSize size = getSize(); + RectF viewport = context.viewport; + + bounds = getBounds(context, new FloatSize(size)); + cropRect = new int[] { 0, size.height, size.width, -size.height }; + bounds.offset(-viewport.left, -viewport.top); + + GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect, + 0); + + float top = viewport.height() - (bounds.top + bounds.height()); + + // There may be errors from a previous GL call, so clear them first because + // we want to check for one below + while (GLES11.glGetError() != GLES11.GL_NO_ERROR); + + GLES11Ext.glDrawTexfOES(bounds.left, top, 0.0f, bounds.width(), bounds.height()); + int error = GLES11.glGetError(); + if (error != GLES11.GL_NO_ERROR) { + Log.i(LOGTAG, "Failed to draw texture: " + error); + } + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/Axis.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/Axis.java new file mode 100644 index 000000000000..1d8138de7d51 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/Axis.java @@ -0,0 +1,271 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * Kartikaya Gupta <kgupta@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.ui; + +import org.mozilla.gecko.util.FloatUtils; + +/** + * This class represents the physics for one axis of movement (i.e. either + * horizontal or vertical). It tracks the different properties of movement + * like displacement, velocity, viewport dimensions, etc. pertaining to + * a particular axis. + */ +abstract class Axis { + // This fraction of velocity remains after every animation frame when the velocity is low. + private static final float FRICTION_SLOW = 0.85f; + // This fraction of velocity remains after every animation frame when the velocity is high. + private static final float FRICTION_FAST = 0.97f; + // Below this velocity (in pixels per frame), the friction starts increasing from FRICTION_FAST + // to FRICTION_SLOW. + private static final float VELOCITY_THRESHOLD = 10.0f; + // The maximum velocity change factor between events, per ms, in %. + // Direction changes are excluded. + private static final float MAX_EVENT_ACCELERATION = 0.012f; + + // The rate of deceleration when the surface has overscrolled. + private static final float OVERSCROLL_DECEL_RATE = 0.04f; + // The percentage of the surface which can be overscrolled before it must snap back. + private static final float SNAP_LIMIT = 0.3f; + + // The minimum amount of space that must be present for an axis to be considered scrollable, + // in pixels. + private static final float MIN_SCROLLABLE_DISTANCE = 0.5f; + // The number of milliseconds per frame assuming 60 fps + private static final float MS_PER_FRAME = 1000.0f / 60.0f; + + private enum FlingStates { + STOPPED, + PANNING, + FLINGING, + } + + private enum Overscroll { + NONE, + MINUS, // Overscrolled in the negative direction + PLUS, // Overscrolled in the positive direction + BOTH, // Overscrolled in both directions (page is zoomed to smaller than screen) + } + + private final SubdocumentScrollHelper mSubscroller; + + private float mFirstTouchPos; /* Position of the first touch event on the current drag. */ + private float mTouchPos; /* Position of the most recent touch event on the current drag. */ + private float mLastTouchPos; /* Position of the touch event before touchPos. */ + private float mVelocity; /* Velocity in this direction; pixels per animation frame. */ + public boolean mScrollingDisabled; /* Whether movement on this axis is locked. */ + private boolean mDisableSnap; /* Whether overscroll snapping is disabled. */ + private float mDisplacement; + + private FlingStates mFlingState; /* The fling state we're in on this axis. */ + + protected abstract float getOrigin(); + protected abstract float getViewportLength(); + protected abstract float getPageLength(); + + Axis(SubdocumentScrollHelper subscroller) { + mSubscroller = subscroller; + } + + private float getViewportEnd() { + return getOrigin() + getViewportLength(); + } + + void startTouch(float pos) { + mVelocity = 0.0f; + mScrollingDisabled = false; + mFirstTouchPos = mTouchPos = mLastTouchPos = pos; + } + + float panDistance(float currentPos) { + return currentPos - mFirstTouchPos; + } + + void setScrollingDisabled(boolean disabled) { + mScrollingDisabled = disabled; + } + + void saveTouchPos() { + mLastTouchPos = mTouchPos; + } + + void updateWithTouchAt(float pos, float timeDelta) { + float newVelocity = (mTouchPos - pos) / timeDelta * MS_PER_FRAME; + + // If there's a direction change, or current velocity is very low, + // allow setting of the velocity outright. Otherwise, use the current + // velocity and a maximum change factor to set the new velocity. + boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f; + boolean directionChange = (mVelocity > 0) != (newVelocity > 0); + if (curVelocityIsLow || (directionChange && !FloatUtils.fuzzyEquals(newVelocity, 0.0f))) { + mVelocity = newVelocity; + } else { + float maxChange = Math.abs(mVelocity * timeDelta * MAX_EVENT_ACCELERATION); + mVelocity = Math.min(mVelocity + maxChange, Math.max(mVelocity - maxChange, newVelocity)); + } + + mTouchPos = pos; + } + + boolean overscrolled() { + return getOverscroll() != Overscroll.NONE; + } + + private Overscroll getOverscroll() { + boolean minus = (getOrigin() < 0.0f); + boolean plus = (getViewportEnd() > getPageLength()); + if (minus && plus) { + return Overscroll.BOTH; + } else if (minus) { + return Overscroll.MINUS; + } else if (plus) { + return Overscroll.PLUS; + } else { + return Overscroll.NONE; + } + } + + // Returns the amount that the page has been overscrolled. If the page hasn't been + // overscrolled on this axis, returns 0. + private float getExcess() { + switch (getOverscroll()) { + case MINUS: return -getOrigin(); + case PLUS: return getViewportEnd() - getPageLength(); + case BOTH: return getViewportEnd() - getPageLength() - getOrigin(); + default: return 0.0f; + } + } + + /* + * Returns true if the page is zoomed in to some degree along this axis such that scrolling is + * possible and this axis has not been scroll locked while panning. Otherwise, returns false. + */ + private boolean scrollable() { + return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE && + !mScrollingDisabled; + } + + /* + * Returns the resistance, as a multiplier, that should be taken into account when + * tracking or pinching. + */ + float getEdgeResistance() { + float excess = getExcess(); + if (excess > 0.0f) { + // excess can be greater than viewport length, but the resistance + // must never drop below 0.0 + return Math.max(0.0f, SNAP_LIMIT - excess / getViewportLength()); + } + return 1.0f; + } + + /* Returns the velocity. If the axis is locked, returns 0. */ + float getRealVelocity() { + return scrollable() ? mVelocity : 0f; + } + + void startPan() { + mFlingState = FlingStates.PANNING; + } + + void startFling(boolean stopped) { + mDisableSnap = mSubscroller.scrolling(); + + if (stopped) { + mFlingState = FlingStates.STOPPED; + } else { + mFlingState = FlingStates.FLINGING; + } + } + + /* Advances a fling animation by one step. */ + boolean advanceFling() { + if (mFlingState != FlingStates.FLINGING) { + return false; + } + if (mSubscroller.scrolling() && !mSubscroller.lastScrollSucceeded()) { + // if the subdocument stopped scrolling, it's because it reached the end + // of the subdocument. we don't do overscroll on subdocuments, so there's + // no point in continuing this fling. + return false; + } + + float excess = getExcess(); + if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f)) { + // If we aren't overscrolled, just apply friction. + if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) { + mVelocity *= FRICTION_FAST; + } else { + float t = mVelocity / VELOCITY_THRESHOLD; + mVelocity *= FloatUtils.interpolate(FRICTION_SLOW, FRICTION_FAST, t); + } + } else { + // Otherwise, decrease the velocity linearly. + float elasticity = 1.0f - excess / (getViewportLength() * SNAP_LIMIT); + if (getOverscroll() == Overscroll.MINUS) { + mVelocity = Math.min((mVelocity + OVERSCROLL_DECEL_RATE) * elasticity, 0.0f); + } else { // must be Overscroll.PLUS + mVelocity = Math.max((mVelocity - OVERSCROLL_DECEL_RATE) * elasticity, 0.0f); + } + } + + return true; + } + + void stopFling() { + mVelocity = 0.0f; + mFlingState = FlingStates.STOPPED; + } + + // Performs displacement of the viewport position according to the current velocity. + void displace() { + if (!mSubscroller.scrolling() && !scrollable()) + return; + + if (mFlingState == FlingStates.PANNING) + mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(); + else + mDisplacement += mVelocity; + } + + float resetDisplacement() { + float d = mDisplacement; + mDisplacement = 0.0f; + return d; + } +}
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/PanZoomController.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/PanZoomController.java new file mode 100644 index 000000000000..58c52e26ff99 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/PanZoomController.java @@ -0,0 +1,929 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009-2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * Kartikaya Gupta <kgupta@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.ui; + +import org.json.JSONObject; +import org.json.JSONException; +import org.libreoffice.LOKitShell; +import org.libreoffice.LibreOfficeMainActivity; +import org.mozilla.gecko.gfx.FloatSize; +import org.mozilla.gecko.gfx.LayerController; +import org.mozilla.gecko.gfx.PointUtils; +import org.mozilla.gecko.gfx.ViewportMetrics; +import org.mozilla.gecko.util.FloatUtils; +//import org.mozilla.gecko.GeckoApp; +//import org.mozilla.gecko.GeckoAppShell; +//import org.mozilla.gecko.GeckoEvent; +import org.mozilla.gecko.GeckoEventListener; +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.FloatMath; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import java.util.Timer; +import java.util.TimerTask; + +/* + * Handles the kinetic scrolling and zooming physics for a layer controller. + * + * Many ideas are from Joe Hewitt's Scrollability: + * https://github.com/joehewitt/scrollability/ + */ +public class PanZoomController + extends GestureDetector.SimpleOnGestureListener + implements SimpleScaleGestureDetector.SimpleScaleGestureListener, GeckoEventListener +{ + private static final String LOGTAG = "GeckoPanZoomController"; + + private static String MESSAGE_ZOOM_RECT = "Browser:ZoomToRect"; + private static String MESSAGE_ZOOM_PAGE = "Browser:ZoomToPageWidth"; + + // Animation stops if the velocity is below this value when overscrolled or panning. + private static final float STOPPED_THRESHOLD = 4.0f; + + // Animation stops is the velocity is below this threshold when flinging. + private static final float FLING_STOPPED_THRESHOLD = 0.1f; + + // The distance the user has to pan before we recognize it as such (e.g. to avoid 1-pixel pans + // between the touch-down and touch-up of a click). In units of density-independent pixels. + public static final float PAN_THRESHOLD = 1/16f * LOKitShell.getDpi(); + + // Angle from axis within which we stay axis-locked + private static final double AXIS_LOCK_ANGLE = Math.PI / 6.0; // 30 degrees + + // The maximum amount we allow you to zoom into a page + private static final float MAX_ZOOM = 12.0f; + + /* 16 precomputed frames of the _ease-out_ animation from the CSS Transitions specification. */ + private static final float[] EASE_OUT_ANIMATION_FRAMES = { + 0.00000f, /* 0 */ + 0.10211f, /* 1 */ + 0.19864f, /* 2 */ + 0.29043f, /* 3 */ + 0.37816f, /* 4 */ + 0.46155f, /* 5 */ + 0.54054f, /* 6 */ + 0.61496f, /* 7 */ + 0.68467f, /* 8 */ + 0.74910f, /* 9 */ + 0.80794f, /* 10 */ + 0.86069f, /* 11 */ + 0.90651f, /* 12 */ + 0.94471f, /* 13 */ + 0.97401f, /* 14 */ + 0.99309f, /* 15 */ + }; + + private enum PanZoomState { + NOTHING, /* no touch-start events received */ + FLING, /* all touches removed, but we're still scrolling page */ + TOUCHING, /* one touch-start event received */ + PANNING_LOCKED, /* touch-start followed by move (i.e. panning with axis lock) */ + PANNING, /* panning without axis lock */ + PANNING_HOLD, /* in panning, but not moving. + * similar to TOUCHING but after starting a pan */ + PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */ + PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */ + ANIMATED_ZOOM /* animated zoom to a new rect */ + } + + private final LayerController mController; + private final SubdocumentScrollHelper mSubscroller; + private final Axis mX; + private final Axis mY; + + private Thread mMainThread; + + /* The timer that handles flings or bounces. */ + private Timer mAnimationTimer; + /* The runnable being scheduled by the animation timer. */ + private AnimationRunnable mAnimationRunnable; + /* The zoom focus at the first zoom event (in page coordinates). */ + private PointF mLastZoomFocus; + /* The time the last motion event took place. */ + private long mLastEventTime; + /* Current state the pan/zoom UI is in. */ + private PanZoomState mState; + + public PanZoomController(LayerController controller) { + mController = controller; + mSubscroller = new SubdocumentScrollHelper(this); + mX = new AxisX(mSubscroller); + mY = new AxisY(mSubscroller); + + mMainThread = /*GeckoApp*/LibreOfficeMainActivity.mAppContext.getMainLooper().getThread(); + checkMainThread(); + + mState = PanZoomState.NOTHING; + + //GeckoAppShell.registerGeckoEventListener(MESSAGE_ZOOM_RECT, this); + //GeckoAppShell.registerGeckoEventListener(MESSAGE_ZOOM_PAGE, this); + } + + // for debugging bug 713011; it can be taken out once that is resolved. + private void checkMainThread() { + if (mMainThread != Thread.currentThread()) { + // log with full stack trace + Log.e(LOGTAG, "Uh-oh, we're running on the wrong thread!", new Exception()); + } + } + + public void handleMessage(String event, JSONObject message) { + Log.i(LOGTAG, "Got message: " + event); + try { + if (MESSAGE_ZOOM_RECT.equals(event)) { + float x = (float)message.getDouble("x"); + float y = (float)message.getDouble("y"); + final RectF zoomRect = new RectF(x, y, + x + (float)message.getDouble("w"), + y + (float)message.getDouble("h")); + mController.post(new Runnable() { + public void run() { + animatedZoomTo(zoomRect); + } + }); + } else if (MESSAGE_ZOOM_PAGE.equals(event)) { + FloatSize pageSize = mController.getPageSize(); + + RectF viewableRect = mController.getViewport(); + float y = viewableRect.top; + // attempt to keep zoom keep focused on the center of the viewport + float newHeight = viewableRect.height() * pageSize.width / viewableRect.width(); + float dh = viewableRect.height() - newHeight; // increase in the height + final RectF r = new RectF(0.0f, + y + dh/2, + pageSize.width, + y + dh/2 + newHeight); + mController.post(new Runnable() { + public void run() { + animatedZoomTo(r); + } + }); + } + } catch (Exception e) { + Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); + } + } + + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: return onTouchStart(event); + case MotionEvent.ACTION_MOVE: return onTouchMove(event); + case MotionEvent.ACTION_UP: return onTouchEnd(event); + case MotionEvent.ACTION_CANCEL: return onTouchCancel(event); + default: return false; + } + } + + /** This function must be called from the UI thread. */ + public void abortAnimation() { + checkMainThread(); + // this happens when gecko changes the viewport on us or if the device is rotated. + // if that's the case, abort any animation in progress and re-zoom so that the page + // snaps to edges. for other cases (where the user's finger(s) are down) don't do + // anything special. + switch (mState) { + case FLING: + mX.stopFling(); + mY.stopFling(); + mState = PanZoomState.NOTHING; + // fall through + case ANIMATED_ZOOM: + // the zoom that's in progress likely makes no sense any more (such as if + // the screen orientation changed) so abort it + // fall through + case NOTHING: + // Don't do animations here; they're distracting and can cause flashes on page + // transitions. + mController.setViewportMetrics(getValidViewportMetrics()); + mController.notifyLayerClientOfGeometryChange(); + break; + } + } + + /** This must be called on the UI thread. */ + public void pageSizeUpdated() { + if (mState == PanZoomState.NOTHING) { + ViewportMetrics validated = getValidViewportMetrics(); + if (! mController.getViewportMetrics().fuzzyEquals(validated)) { + // page size changed such that we are now in overscroll. snap to the + // the nearest valid viewport + mController.setViewportMetrics(validated); + mController.notifyLayerClientOfGeometryChange(); + } + } + } + + /* + * Panning/scrolling + */ + private boolean onTouchStart(MotionEvent event) { + Log.d(LOGTAG, "onTouchStart in state " + mState); + // user is taking control of movement, so stop + // any auto-movement we have going + stopAnimationTimer(); + mSubscroller.cancel(); + + switch (mState) { + case ANIMATED_ZOOM: + return false; + case FLING: + case NOTHING: + startTouch(event.getX(0), event.getY(0), event.getEventTime()); + return false; + case TOUCHING: + case PANNING: + case PANNING_LOCKED: + case PANNING_HOLD: + case PANNING_HOLD_LOCKED: + case PINCHING: + Log.e(LOGTAG, "Received impossible touch down while in " + mState); + return false; + } + Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchStart"); + return false; + } + + private boolean onTouchMove(MotionEvent event) { + Log.d(LOGTAG, "onTouchMove in state " + mState); + + switch (mState) { + case NOTHING: + case FLING: + // should never happen + Log.e(LOGTAG, "Received impossible touch move while in " + mState); + return false; + + case TOUCHING: + if (panDistance(event) < PAN_THRESHOLD) { + return false; + } + cancelTouch(); + startPanning(event.getX(0), event.getY(0), event.getEventTime()); + //GeckoApp.mAppContext.hidePlugins(false /* don't hide layers */); + //GeckoApp.mFormAssistPopup.hide(); + track(event); + return true; + + case PANNING_HOLD_LOCKED: + //GeckoApp.mFormAssistPopup.hide(); + mState = PanZoomState.PANNING_LOCKED; + // fall through + case PANNING_LOCKED: + track(event); + return true; + + case PANNING_HOLD: + //GeckoApp.mFormAssistPopup.hide(); + mState = PanZoomState.PANNING; + // fall through + case PANNING: + track(event); + return true; + + case ANIMATED_ZOOM: + case PINCHING: + // scale gesture listener will handle this + return false; + } + Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchMove"); + return false; + } + + private boolean onTouchEnd(MotionEvent event) { + Log.d(LOGTAG, "onTouchEnd in " + mState); + + switch (mState) { + case NOTHING: + case FLING: + // should never happen + Log.e(LOGTAG, "Received impossible touch end while in " + mState); + return false; + case TOUCHING: + mState = PanZoomState.NOTHING; + // the switch into TOUCHING might have happened while the page was + // snapping back after overscroll. we need to finish the snap if that + // was the case + bounce(); + return false; + case PANNING: + case PANNING_LOCKED: + case PANNING_HOLD: + case PANNING_HOLD_LOCKED: + mState = PanZoomState.FLING; + fling(); + return true; + case PINCHING: + mState = PanZoomState.NOTHING; + return true; + case ANIMATED_ZOOM: + return false; + } + Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchEnd"); + return false; + } + + private boolean onTouchCancel(MotionEvent event) { + Log.d(LOGTAG, "onTouchCancel in " + mState); + + mState = PanZoomState.NOTHING; + // ensure we snap back if we're overscrolled + bounce(); + return false; + } + + private void startTouch(float x, float y, long time) { + mX.startTouch(x); + mY.startTouch(y); + mState = PanZoomState.TOUCHING; + mLastEventTime = time; + } + + private void startPanning(float x, float y, long time) { + float dx = mX.panDistance(x); + float dy = mY.panDistance(y); + double angle = Math.atan2(dy, dx); // range [-pi, pi] + angle = Math.abs(angle); // range [0, pi] + + // When the touch move breaks through the pan threshold, reposition the touch down origin + // so the page won't jump when we start panning. + mX.startTouch(x); + mY.startTouch(y); + mLastEventTime = time; + + if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) { + mY.setScrollingDisabled(true); + mState = PanZoomState.PANNING_LOCKED; + } else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) { + mX.setScrollingDisabled(true); + mState = PanZoomState.PANNING_LOCKED; + } else { + mState = PanZoomState.PANNING; + } + } + + private float panDistance(MotionEvent move) { + float dx = mX.panDistance(move.getX(0)); + float dy = mY.panDistance(move.getY(0)); + return FloatMath.sqrt(dx * dx + dy * dy); + } + + private void track(float x, float y, long time) { + float timeDelta = (float)(time - mLastEventTime); + if (FloatUtils.fuzzyEquals(timeDelta, 0)) { + // probably a duplicate event, ignore it. using a zero timeDelta will mess + // up our velocity + return; + } + mLastEventTime = time; + + mX.updateWithTouchAt(x, timeDelta); + mY.updateWithTouchAt(y, timeDelta); + } + + private void track(MotionEvent event) { + mX.saveTouchPos(); + mY.saveTouchPos(); + + for (int i = 0; i < event.getHistorySize(); i++) { + track(event.getHistoricalX(0, i), + event.getHistoricalY(0, i), + event.getHistoricalEventTime(i)); + } + track(event.getX(0), event.getY(0), event.getEventTime()); + + if (stopped()) { + if (mState == PanZoomState.PANNING) { + mState = PanZoomState.PANNING_HOLD; + } else if (mState == PanZoomState.PANNING_LOCKED) { + mState = PanZoomState.PANNING_HOLD_LOCKED; + } else { + // should never happen, but handle anyway for robustness + Log.e(LOGTAG, "Impossible case " + mState + " when stopped in track"); + mState = PanZoomState.PANNING_HOLD_LOCKED; + } + } + + mX.startPan(); + mY.startPan(); + updatePosition(); + } + + private void fling() { + updatePosition(); + + stopAnimationTimer(); + + boolean stopped = stopped(); + mX.startFling(stopped); + mY.startFling(stopped); + + startAnimationTimer(new FlingRunnable()); + } + + /* Performs a bounce-back animation to the given viewport metrics. */ + private void bounce(ViewportMetrics metrics) { + stopAnimationTimer(); + + ViewportMetrics bounceStartMetrics = new ViewportMetrics(mController.getViewportMetrics()); + if (bounceStartMetrics.fuzzyEquals(metrics)) { + mState = PanZoomState.NOTHING; + return; + } + + mState = PanZoomState.FLING; + Log.d(LOGTAG, "end bounce at " + metrics); + + startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics)); + } + + /* Performs a bounce-back animation to the nearest valid viewport metrics. */ + private void bounce() { + bounce(getValidViewportMetrics()); + } + + /* Starts the fling or bounce animation. */ + private void startAnimationTimer(final AnimationRunnable runnable) { + if (mAnimationTimer != null) { + Log.e(LOGTAG, "Attempted to start a new fling without canceling the old one!"); + stopAnimationTimer(); + } + + //GeckoApp.mAppContext.hidePlugins(false /* don't hide layers */); + + mAnimationTimer = new Timer("Animation Timer"); + mAnimationRunnable = runnable; + mAnimationTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { mController.post(runnable); } + }, 0, 1000L/60L); + } + + /* Stops the fling or bounce animation. */ + private void stopAnimationTimer() { + if (mAnimationTimer != null) { + mAnimationTimer.cancel(); + mAnimationTimer = null; + } + if (mAnimationRunnable != null) { + mAnimationRunnable.terminate(); + mAnimationRunnable = null; + } + + //GeckoApp.mAppContext.showPlugins(); + } + + private float getVelocity() { + float xvel = mX.getRealVelocity(); + float yvel = mY.getRealVelocity(); + return FloatMath.sqrt(xvel * xvel + yvel * yvel); + } + + private boolean stopped() { + return getVelocity() < STOPPED_THRESHOLD; + } + + PointF getDisplacement() { + return new PointF(mX.resetDisplacement(), mY.resetDisplacement()); + } + + private void updatePosition() { + mX.displace(); + mY.displace(); + PointF displacement = getDisplacement(); + if (! mSubscroller.scrollBy(displacement)) { + synchronized (mController) { + mController.scrollBy(displacement); + } + } + } + + private abstract class AnimationRunnable implements Runnable { + private boolean mAnimationTerminated; + + /* This should always run on the UI thread */ + public final void run() { + /* + * Since the animation timer queues this runnable on the UI thread, it + * is possible that even when the animation timer is cancelled, there + * are multiple instances of this queued, so we need to have another + * mechanism to abort. This is done by using the mAnimationTerminated flag. + */ + if (mAnimationTerminated) { + return; + } + animateFrame(); + } + + protected abstract void animateFrame(); + + /* This should always run on the UI thread */ + protected final void terminate() { + mAnimationTerminated = true; + } + } + + /* The callback that performs the bounce animation. */ + private class BounceRunnable extends AnimationRunnable { + /* The current frame of the bounce-back animation */ + private int mBounceFrame; + /* + * The viewport metrics that represent the start and end of the bounce-back animation, + * respectively. + */ + private ViewportMetrics mBounceStartMetrics; + private ViewportMetrics mBounceEndMetrics; + + BounceRunnable(ViewportMetrics startMetrics, ViewportMetrics endMetrics) { + mBounceStartMetrics = startMetrics; + mBounceEndMetrics = endMetrics; + } + + protected void animateFrame() { + /* + * The pan/zoom controller might have signaled to us that it wants to abort the + * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail + * out. + */ + if (mState != PanZoomState.FLING) { + finishAnimation(); + return; + } + + /* Perform the next frame of the bounce-back animation. */ + if (mBounceFrame < EASE_OUT_ANIMATION_FRAMES.length) { + advanceBounce(); + return; + } + + /* Finally, if there's nothing else to do, complete the animation and go to sleep. */ + finishBounce(); + finishAnimation(); + mState = PanZoomState.NOTHING; + } + + /* Performs one frame of a bounce animation. */ + private void advanceBounce() { + synchronized (mController) { + float t = EASE_OUT_ANIMATION_FRAMES[mBounceFrame]; + ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t); + mController.setViewportMetrics(newMetrics); + mController.notifyLayerClientOfGeometryChange(); + mBounceFrame++; + } + } + + /* Concludes a bounce animation and snaps the viewport into place. */ + private void finishBounce() { + synchronized (mController) { + mController.setViewportMetrics(mBounceEndMetrics); + mController.notifyLayerClientOfGeometryChange(); + mBounceFrame = -1; + } + } + } + + // The callback that performs the fling animation. + private class FlingRunnable extends AnimationRunnable { + protected void animateFrame() { + /* + * The pan/zoom controller might have signaled to us that it wants to abort the + * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail + * out. + */ + if (mState != PanZoomState.FLING) { + finishAnimation(); + return; + } + + /* Advance flings, if necessary. */ + boolean flingingX = mX.advanceFling(); + boolean flingingY = mY.advanceFling(); + + boolean overscrolled = (mX.overscrolled() || mY.overscrolled()); + + /* If we're still flinging in any direction, update the origin. */ + if (flingingX || flingingY) { + updatePosition(); + + /* + * Check to see if we're still flinging with an appreciable velocity. The threshold is + * higher in the case of overscroll, so we bounce back eagerly when overscrolling but + * coast smoothly to a stop when not. In other words, require a greater velocity to + * maintain the fling once we enter overscroll. + */ + float threshold = (overscrolled && !mSubscroller.scrolling() ? STOPPED_THRESHOLD : FLING_STOPPED_THRESHOLD); + if (getVelocity() >= threshold) { + // we're still flinging + return; + } + + mX.stopFling(); + mY.stopFling(); + } + + /* Perform a bounce-back animation if overscrolled. */ + if (overscrolled) { + bounce(); + } else { + finishAnimation(); + mState = PanZoomState.NOTHING; + } + } + } + + private void finishAnimation() { + checkMainThread(); + + Log.d(LOGTAG, "Finishing animation at " + mController.getViewportMetrics()); + stopAnimationTimer(); + + // Force a viewport synchronisation + //GeckoApp.mAppContext.showPlugins(); + mController.setForceRedraw(); + mController.notifyLayerClientOfGeometryChange(); + } + + /* Returns the nearest viewport metrics with no overscroll visible. */ + private ViewportMetrics getValidViewportMetrics() { + return getValidViewportMetrics(new ViewportMetrics(mController.getViewportMetrics())); + } + + private ViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) { + Log.d(LOGTAG, "generating valid viewport using " + viewportMetrics); + + /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */ + float zoomFactor = viewportMetrics.getZoomFactor(); + FloatSize pageSize = viewportMetrics.getPageSize(); + RectF viewport = viewportMetrics.getViewport(); + + float focusX = viewport.width() / 2.0f; + float focusY = viewport.height() / 2.0f; + float minZoomFactor = 0.0f; + if (viewport.width() > pageSize.width && pageSize.width > 0) { + float scaleFactor = viewport.width() / pageSize.width; + minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor); + focusX = 0.0f; + } + if (viewport.height() > pageSize.height && pageSize.height > 0) { + float scaleFactor = viewport.height() / pageSize.height; + minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor); + focusY = 0.0f; + } + + if (!FloatUtils.fuzzyEquals(minZoomFactor, 0.0f)) { + // if one (or both) of the page dimensions is smaller than the viewport, + // zoom using the top/left as the focus on that axis. this prevents the + // scenario where, if both dimensions are smaller than the viewport, but + // by different scale factors, we end up scrolled to the end on one axis + // after applying the scale + PointF center = new PointF(focusX, focusY); + viewportMetrics.scaleTo(minZoomFactor, center); + } else if (zoomFactor > MAX_ZOOM) { + PointF center = new PointF(viewport.width() / 2.0f, viewport.height() / 2.0f); + viewportMetrics.scaleTo(MAX_ZOOM, center); + } + + /* Now we pan to the right origin. */ + viewportMetrics.setViewport(viewportMetrics.getClampedViewport()); + Log.d(LOGTAG, "generated valid viewport as " + viewportMetrics); + + return viewportMetrics; + } + + private class AxisX extends Axis { + AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); } + @Override + public float getOrigin() { return mController.getOrigin().x; } + @Override + protected float getViewportLength() { return mController.getViewportSize().width; } + @Override + protected float getPageLength() { return mController.getPageSize().width; } + } + + private class AxisY extends Axis { + AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); } + @Override + public float getOrigin() { return mController.getOrigin().y; } + @Override + protected float getViewportLength() { return mController.getViewportSize().height; } + @Override + protected float getPageLength() { return mController.getPageSize().height; } + } + + /* + * Zooming + */ + @Override + public boolean onScaleBegin(SimpleScaleGestureDetector detector) { + Log.d(LOGTAG, "onScaleBegin in " + mState); + + if (mState == PanZoomState.ANIMATED_ZOOM) + return false; + + mState = PanZoomState.PINCHING; + mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY()); + //GeckoApp.mAppContext.hidePlugins(false /* don't hide layers, only views */); + //GeckoApp.mFormAssistPopup.hide(); + cancelTouch(); + + return true; + } + + @Override + public boolean onScale(SimpleScaleGestureDetector detector) { + Log.d(LOGTAG, "onScale in state " + mState); + + //if (GeckoApp.mDOMFullScreen) + // return false; + + if (!mController.getViewportMetrics().getAllowZoom()) + return false; + + if (mState == PanZoomState.ANIMATED_ZOOM) + return false; + + float prevSpan = detector.getPreviousSpan(); + if (FloatUtils.fuzzyEquals(prevSpan, 0.0f)) { + // let's eat this one to avoid setting the new zoom to infinity (bug 711453) + return true; + } + + float spanRatio = detector.getCurrentSpan() / prevSpan; + + /* + * Apply edge resistance if we're zoomed out smaller than the page size by scaling the zoom + * factor toward 1.0. + */ + float resistance = Math.min(mX.getEdgeResistance(), mY.getEdgeResistance()); + if (spanRatio > 1.0f) + spanRatio = 1.0f + (spanRatio - 1.0f) * resistance; + else + spanRatio = 1.0f - (1.0f - spanRatio) * resistance; + + synchronized (mController) { + float newZoomFactor = mController.getZoomFactor() * spanRatio; + if (newZoomFactor >= MAX_ZOOM) { + // apply resistance when zooming past MAX_ZOOM, + // such that it asymptotically reaches MAX_ZOOM + 1.0 + // but never exceeds that + float excessZoom = newZoomFactor - MAX_ZOOM; + excessZoom = 1.0f - (float)Math.exp(-excessZoom); + newZoomFactor = MAX_ZOOM + excessZoom; + } + + mController.scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(), + mLastZoomFocus.y - detector.getFocusY())); + PointF focus = new PointF(detector.getFocusX(), detector.getFocusY()); + mController.scaleWithFocus(newZoomFactor, focus); + } + + mLastZoomFocus.set(detector.getFocusX(), detector.getFocusY()); + + return true; + } + + @Override + public void onScaleEnd(SimpleScaleGestureDetector detector) { + Log.d(LOGTAG, "onScaleEnd in " + mState); + + if (mState == PanZoomState.ANIMATED_ZOOM) + return; + + // switch back to the touching state + startTouch(detector.getFocusX(), detector.getFocusY(), detector.getEventTime()); + + // Force a viewport synchronisation + //GeckoApp.mAppContext.showPlugins(); + mController.setForceRedraw(); + mController.notifyLayerClientOfGeometryChange(); + } + + public boolean getRedrawHint() { + return (mState == PanZoomState.NOTHING || mState == PanZoomState.FLING); + } + + private void sendPointToGecko(String event, MotionEvent motionEvent) { + String json; + try { + PointF point = new PointF(motionEvent.getX(), motionEvent.getY()); + point = mController.convertViewPointToLayerPoint(point); + if (point == null) { + return; + } + json = PointUtils.toJSON(point).toString(); + } catch (Exception e) { + Log.e(LOGTAG, "Unable to convert point to JSON for " + event, e); + return; + } + + //GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(event, json)); + } + + @Override + public void onLongPress(MotionEvent motionEvent) { + sendPointToGecko("Gesture:LongPress", motionEvent); + } + + @Override + public boolean onDown(MotionEvent motionEvent) { + sendPointToGecko("Gesture:ShowPress", motionEvent); + return false; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent motionEvent) { + //GeckoApp.mFormAssistPopup.hide(); + sendPointToGecko("Gesture:SingleTap", motionEvent); + return true; + } + + @Override + public boolean onDoubleTap(MotionEvent motionEvent) { + sendPointToGecko("Gesture:DoubleTap", motionEvent); + return true; + } + + public void cancelTouch() { + //GeckoEvent e = GeckoEvent.createBroadcastEvent("Gesture:CancelTouch", ""); + //GeckoAppShell.sendEventToGecko(e); + } + + private boolean animatedZoomTo(RectF zoomToRect) { + //GeckoApp.mFormAssistPopup.hide(); + + mState = PanZoomState.ANIMATED_ZOOM; + final float startZoom = mController.getZoomFactor(); + + RectF viewport = mController.getViewport(); + // 1. adjust the aspect ratio of zoomToRect to match that of the current viewport, + // enlarging as necessary (if it gets too big, it will get shrunk in the next step). + // while enlarging make sure we enlarge equally on both sides to keep the target rect + // centered. + float targetRatio = viewport.width() / viewport.height(); + float rectRatio = zoomToRect.width() / zoomToRect.height(); + if (FloatUtils.fuzzyEquals(targetRatio, rectRatio)) { + // all good, do nothing + } else if (targetRatio < rectRatio) { + // need to increase zoomToRect height + float newHeight = zoomToRect.width() / targetRatio; + zoomToRect.top -= (newHeight - zoomToRect.height()) / 2; + zoomToRect.bottom = zoomToRect.top + newHeight; + } else { // targetRatio > rectRatio) { + // need to increase zoomToRect width + float newWidth = targetRatio * zoomToRect.height(); + zoomToRect.left -= (newWidth - zoomToRect.width()) / 2; + zoomToRect.right = zoomToRect.left + newWidth; + } + + float finalZoom = viewport.width() * startZoom / zoomToRect.width(); + + ViewportMetrics finalMetrics = new ViewportMetrics(mController.getViewportMetrics()); + finalMetrics.setOrigin(new PointF(zoomToRect.left, zoomToRect.top)); + finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f)); + + // 2. now run getValidViewportMetrics on it, so that the target viewport is + // clamped down to prevent overscroll, over-zoom, and other bad conditions. + finalMetrics = getValidViewportMetrics(finalMetrics); + + bounce(finalMetrics); + return true; + } +}
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/SimpleScaleGestureDetector.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/SimpleScaleGestureDetector.java new file mode 100644 index 000000000000..5184bd18a462 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/SimpleScaleGestureDetector.java @@ -0,0 +1,332 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Walton <pcwalton@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.ui; + +import org.mozilla.gecko.gfx.PointUtils; +import org.json.JSONException; +import android.graphics.PointF; +import android.util.Log; +import android.view.MotionEvent; +import java.util.LinkedList; +import java.util.ListIterator; +import java.util.Stack; + +/** + * A less buggy, and smoother, replacement for the built-in Android ScaleGestureDetector. + * + * This gesture detector is more reliable than the built-in ScaleGestureDetector because: + * + * - It doesn't assume that pointer IDs are numbered 0 and 1. + * + * - It doesn't attempt to correct for "slop" when resting one's hand on the device. On some + * devices (e.g. the Droid X) this can cause the ScaleGestureDetector to lose track of how many + * pointers are down, with disastrous results (bug 706684). + * + * - Cancelling a zoom into a pan is handled correctly. + * + * - Starting with three or more fingers down, releasing fingers so that only two are down, and + * then performing a scale gesture is handled correctly. + * + * - It doesn't take pressure into account, which results in smoother scaling. + */ +public class SimpleScaleGestureDetector { + private static final String LOGTAG = "GeckoSimpleScaleGestureDetector"; + + private SimpleScaleGestureListener mListener; + private long mLastEventTime; + + /* Information about all pointers that are down. */ + private LinkedList<PointerInfo> mPointerInfo; + + /** Creates a new gesture detector with the given listener. */ + public SimpleScaleGestureDetector(SimpleScaleGestureListener listener) { + mListener = listener; + mPointerInfo = new LinkedList<PointerInfo>(); + } + + /** Forward touch events to this function. */ + public void onTouchEvent(MotionEvent event) { + switch (event.getAction() & event.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + onTouchStart(event); + break; + case MotionEvent.ACTION_MOVE: + onTouchMove(event); + break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + onTouchEnd(event); + break; + } + } + + private int getPointersDown() { + return mPointerInfo.size(); + } + + private int getActionIndex(MotionEvent event) { + return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + } + + private void onTouchStart(MotionEvent event) { + mLastEventTime = event.getEventTime(); + mPointerInfo.addFirst(PointerInfo.create(event, getActionIndex(event))); + if (getPointersDown() == 2) { + sendScaleGesture(EventType.BEGIN); + } + } + + private void onTouchMove(MotionEvent event) { + mLastEventTime = event.getEventTime(); + for (int i = 0; i < event.getPointerCount(); i++) { + PointerInfo pointerInfo = pointerInfoForEventIndex(event, i); + if (pointerInfo != null) { + pointerInfo.populate(event, i); + } + } + + if (getPointersDown() == 2) { + sendScaleGesture(EventType.CONTINUE); + } + } + + private void onTouchEnd(MotionEvent event) { + mLastEventTime = event.getEventTime(); + + int id = event.getPointerId(getActionIndex(event)); + ListIterator<PointerInfo> iterator = mPointerInfo.listIterator(); + while (iterator.hasNext()) { + PointerInfo pointerInfo = iterator.next(); + if (pointerInfo.getId() != id) { + continue; + } + + // One of the pointers we were tracking was lifted. Remove its info object from the + // list, recycle it to avoid GC pauses, and send an onScaleEnd() notification if this + // ended the gesture. + iterator.remove(); + pointerInfo.recycle(); + if (getPointersDown() == 1) { + sendScaleGesture(EventType.END); + } + } + } + + /** + * Returns the X coordinate of the focus location (the midpoint of the two fingers). If only + * one finger is down, returns the location of that finger. + */ + public float getFocusX() { + switch (getPointersDown()) { + case 1: + return mPointerInfo.getFirst().getCurrent().x; + case 2: + PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast(); + return (pointerA.getCurrent().x + pointerB.getCurrent().x) / 2.0f; + } + + Log.e(LOGTAG, "No gesture taking place in getFocusX()!"); + return 0.0f; + } + + /** + * Returns the Y coordinate of the focus location (the midpoint of the two fingers). If only + * one finger is down, returns the location of that finger. + */ + public float getFocusY() { + switch (getPointersDown()) { + case 1: + return mPointerInfo.getFirst().getCurrent().y; + case 2: + PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast(); + return (pointerA.getCurrent().y + pointerB.getCurrent().y) / 2.0f; + } + + Log.e(LOGTAG, "No gesture taking place in getFocusY()!"); + return 0.0f; + } + + /** Returns the most recent distance between the two pointers. */ + public float getCurrentSpan() { + if (getPointersDown() != 2) { + Log.e(LOGTAG, "No gesture taking place in getCurrentSpan()!"); + return 0.0f; + } + + PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast(); + return PointUtils.distance(pointerA.getCurrent(), pointerB.getCurrent()); + } + + /** Returns the second most recent distance between the two pointers. */ + public float getPreviousSpan() { + if (getPointersDown() != 2) { + Log.e(LOGTAG, "No gesture taking place in getPreviousSpan()!"); + return 0.0f; + } + + PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast(); + PointF a = pointerA.getPrevious(), b = pointerB.getPrevious(); + if (a == null || b == null) { + a = pointerA.getCurrent(); + b = pointerB.getCurrent(); + } + + return PointUtils.distance(a, b); + } + + /** Returns the time of the last event related to the gesture. */ + public long getEventTime() { + return mLastEventTime; + } + + /** Returns true if the scale gesture is in progress and false otherwise. */ + public boolean isInProgress() { + return getPointersDown() == 2; + } + + /* Sends the requested scale gesture notification to the listener. */ + private void sendScaleGesture(EventType eventType) { + switch (eventType) { + case BEGIN: mListener.onScaleBegin(this); break; + case CONTINUE: mListener.onScale(this); break; + case END: mListener.onScaleEnd(this); break; + } + } + + /* + * Returns the pointer info corresponding to the given pointer index, or null if the pointer + * isn't one that's being tracked. + */ + private PointerInfo pointerInfoForEventIndex(MotionEvent event, int index) { + int id = event.getPointerId(index); + for (PointerInfo pointerInfo : mPointerInfo) { + if (pointerInfo.getId() == id) { + return pointerInfo; + } + } + return null; + } + + private enum EventType { + BEGIN, + CONTINUE, + END, + } + + /* Encapsulates information about one of the two fingers involved in the gesture. */ + private static class PointerInfo { + /* A free list that recycles pointer info objects, to reduce GC pauses. */ + private static Stack<PointerInfo> sPointerInfoFreeList; + + private int mId; + private PointF mCurrent, mPrevious; + + private PointerInfo() { + // External users should use create() instead. + } + + /* Creates or recycles a new PointerInfo instance from an event and a pointer index. */ + public static PointerInfo create(MotionEvent event, int index) { + if (sPointerInfoFreeList == null) { + sPointerInfoFreeList = new Stack<PointerInfo>(); + } + + PointerInfo pointerInfo; + if (sPointerInfoFreeList.empty()) { + pointerInfo = new PointerInfo(); + } else { + pointerInfo = sPointerInfoFreeList.pop(); + } + + pointerInfo.populate(event, index); + return pointerInfo; + } + + /* + * Fills in the fields of this instance from the given motion event and pointer index + * within that event. + */ + public void populate(MotionEvent event, int index) { + mId = event.getPointerId(index); + mPrevious = mCurrent; + mCurrent = new PointF(event.getX(index), event.getY(index)); + } + + public void recycle() { + mId = -1; + mPrevious = mCurrent = null; + sPointerInfoFreeList.push(this); + } + + public int getId() { return mId; } + public PointF getCurrent() { return mCurrent; } + public PointF getPrevious() { return mPrevious; } + + @Override + public String toString() { + if (mId == -1) { + return "(up)"; + } + + try { + String prevString; + if (mPrevious == null) { + prevString = "n/a"; + } else { + prevString = PointUtils.toJSON(mPrevious).toString(); + } + + // The current position should always be non-null. + String currentString = PointUtils.toJSON(mCurrent).toString(); + return "id=" + mId + " cur=" + currentString + " prev=" + prevString; + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + } + + public static interface SimpleScaleGestureListener { + public boolean onScale(SimpleScaleGestureDetector detector); + public boolean onScaleBegin(SimpleScaleGestureDetector detector); + public void onScaleEnd(SimpleScaleGestureDetector detector); + } +} + diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java new file mode 100644 index 000000000000..c7da61acda2b --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java @@ -0,0 +1,140 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Kartikaya Gupta <kgupta@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko.ui; + +//import org.mozilla.gecko.GeckoAppShell; +//import org.mozilla.gecko.GeckoEvent; +import org.mozilla.gecko.GeckoEventListener; +import org.json.JSONObject; +import org.json.JSONException; +import android.graphics.PointF; +import android.os.Handler; +import android.util.Log; + +class SubdocumentScrollHelper implements GeckoEventListener { + private static final String LOGTAG = "GeckoSubdocumentScrollHelper"; + + private static String MESSAGE_PANNING_OVERRIDE = "Panning:Override"; + private static String MESSAGE_CANCEL_OVERRIDE = "Panning:CancelOverride"; + private static String MESSAGE_SCROLL = "Gesture:Scroll"; + private static String MESSAGE_SCROLL_ACK = "Gesture:ScrollAck"; + + private final PanZoomController mPanZoomController; + private final Handler mUiHandler; + + private boolean mOverridePanning; + private boolean mOverrideScrollAck; + private boolean mOverrideScrollPending; + private boolean mScrollSucceeded; + + SubdocumentScrollHelper(PanZoomController controller) { + mPanZoomController = controller; + // mUiHandler will be bound to the UI thread since that's where this constructor runs + mUiHandler = new Handler(); + + //GeckoAppShell.registerGeckoEventListener(MESSAGE_PANNING_OVERRIDE, this); + //GeckoAppShell.registerGeckoEventListener(MESSAGE_CANCEL_OVERRIDE, this); + //GeckoAppShell.registerGeckoEventListener(MESSAGE_SCROLL_ACK, this); + } + + boolean scrollBy(PointF displacement) { + if (! mOverridePanning) { + return false; + } + + if (! mOverrideScrollAck) { + mOverrideScrollPending = true; + return true; + } + + mOverrideScrollAck = false; + mOverrideScrollPending = false; + + JSONObject json = new JSONObject(); + try { + json.put("x", displacement.x); + json.put("y", displacement.y); + } catch (JSONException e) { + Log.e(LOGTAG, "Error forming subwindow scroll message: ", e); + } + //GeckoAppShell.sendEventToGecko(new GeckoEvent(MESSAGE_SCROLL, json.toString())); + + return true; + } + + void cancel() { + mOverridePanning = false; + } + + boolean scrolling() { + return mOverridePanning; + } + + boolean lastScrollSucceeded() { + return mScrollSucceeded; + } + + // GeckoEventListener implementation + + public void handleMessage(final String event, final JSONObject message) { + // this comes in on the gecko thread; hand off the handling to the UI thread + mUiHandler.post(new Runnable() { + public void run() { + Log.i(LOGTAG, "Got message: " + event); + try { + if (MESSAGE_PANNING_OVERRIDE.equals(event)) { + mOverridePanning = true; + mOverrideScrollAck = true; + mOverrideScrollPending = false; + mScrollSucceeded = true; + } else if (MESSAGE_CANCEL_OVERRIDE.equals(event)) { + mOverridePanning = false; + } else if (MESSAGE_SCROLL_ACK.equals(event)) { + mOverrideScrollAck = true; + mScrollSucceeded = message.getBoolean("scrolled"); + if (mOverridePanning && mOverrideScrollPending) { + scrollBy(mPanZoomController.getDisplacement()); + } + } + } catch (Exception e) { + Log.e(LOGTAG, "Exception handling message", e); + } + } + }); + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/util/FloatUtils.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/util/FloatUtils.java new file mode 100644 index 000000000000..b4bb249c4d5f --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/util/FloatUtils.java @@ -0,0 +1,43 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.util; + +import android.graphics.PointF; + +import java.lang.IllegalArgumentException; + +public final class FloatUtils { + private FloatUtils() {} + + public static boolean fuzzyEquals(float a, float b) { + return (Math.abs(a - b) < 1e-6); + } + + public static boolean fuzzyEquals(PointF a, PointF b) { + return fuzzyEquals(a.x, b.x) && fuzzyEquals(a.y, b.y); + } + + /* + * Returns the value that represents a linear transition between `from` and `to` at time `t`, + * which is on the scale [0, 1). Thus with t = 0.0f, this returns `from`; with t = 1.0f, this + * returns `to`; with t = 0.5f, this returns the value halfway from `from` to `to`. + */ + public static float interpolate(float from, float to, float t) { + return from + (to - from) * t; + } + + /** + * Returns 'value', clamped so that it isn't any lower than 'low', and it + * isn't any higher than 'high'. + */ + public static float clamp(float value, float low, float high) { + if (high < low) { + throw new IllegalArgumentException( + "clamp called with invalid parameters (" + high + " < " + low + ")" ); + } + return Math.max(low, Math.min(high, value)); + } +} diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/base.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/base.png Binary files differnew file mode 100644 index 000000000000..729dbcd82ebf --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/base.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/calc.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/calc.png Binary files differnew file mode 100644 index 000000000000..a3f5fd4d80c0 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/calc.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/draw.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/draw.png Binary files differnew file mode 100644 index 000000000000..b3ee11426a04 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/draw.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/dummy_page.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/dummy_page.png Binary files differnew file mode 100644 index 000000000000..c58d276e7085 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/dummy_page.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/ic_launcher.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..96a442e5b8e9 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/ic_launcher.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/ic_status_logo.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/ic_status_logo.png Binary files differnew file mode 100644 index 000000000000..d5f16694f342 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/ic_status_logo.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/impress.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/impress.png Binary files differnew file mode 100644 index 000000000000..5909f05bf089 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/impress.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/lo_icon.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/lo_icon.png Binary files differnew file mode 100644 index 000000000000..2ef86417e69e --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/lo_icon.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/main.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/main.png Binary files differnew file mode 100644 index 000000000000..7e8e2a05e2da --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/main.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/math.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/math.png Binary files differnew file mode 100644 index 000000000000..50b8dc863bff --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/math.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/startcenter.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/startcenter.png Binary files differnew file mode 100644 index 000000000000..7e8e2a05e2da --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-hdpi/startcenter.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/background.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/background.png Binary files differnew file mode 100644 index 000000000000..611592b5167a --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/background.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/base.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/base.png Binary files differnew file mode 100644 index 000000000000..729dbcd82ebf --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/base.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/calc.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/calc.png Binary files differnew file mode 100644 index 000000000000..a3f5fd4d80c0 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/calc.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/docu.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/docu.png Binary files differnew file mode 100644 index 000000000000..ab34ae5638e1 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/docu.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/draw.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/draw.png Binary files differnew file mode 100644 index 000000000000..b3ee11426a04 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/draw.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/ic_launcher.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..359047dfa4ed --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/ic_launcher.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/ic_status_logo.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/ic_status_logo.png Binary files differnew file mode 100644 index 000000000000..835fc9290727 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/ic_status_logo.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/impress.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/impress.png Binary files differnew file mode 100644 index 000000000000..5909f05bf089 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/impress.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/lo_icon.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/lo_icon.png Binary files differnew file mode 100644 index 000000000000..4f3f89beadc2 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/lo_icon.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/shadow.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/shadow.png Binary files differnew file mode 100644 index 000000000000..3ce69155c6b5 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/shadow.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/writer.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/writer.png Binary files differnew file mode 100644 index 000000000000..2f4abcb280cd --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-mdpi/writer.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/base.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/base.png Binary files differnew file mode 100644 index 000000000000..729dbcd82ebf --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/base.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/calc.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/calc.png Binary files differnew file mode 100644 index 000000000000..a3f5fd4d80c0 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/calc.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/draw.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/draw.png Binary files differnew file mode 100644 index 000000000000..b3ee11426a04 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/draw.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/ic_launcher.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..71c6d760f051 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/ic_launcher.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/ic_status_logo.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/ic_status_logo.png Binary files differnew file mode 100644 index 000000000000..c8005425416a --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/ic_status_logo.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/impress.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/impress.png Binary files differnew file mode 100644 index 000000000000..5909f05bf089 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/impress.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/writer.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/writer.png Binary files differnew file mode 100644 index 000000000000..2f4abcb280cd --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-xhdpi/writer.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/android/experimental/LOAndroid2/app/src/main/res/drawable-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..4df18946442e --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/drawable-xxhdpi/ic_launcher.png diff --git a/android/experimental/LOAndroid2/app/src/main/res/layout/activity_main.xml b/android/experimental/LOAndroid2/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000000..7b53d58a2b5c --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/main_layout" + android:background="#fff" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <RelativeLayout + android:id="@+id/gecko_layout" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1"/> + +</LinearLayout>
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/app/src/main/res/menu/main.xml b/android/experimental/LOAndroid2/app/src/main/res/menu/main.xml new file mode 100644 index 000000000000..6768fd32a890 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/menu/main.xml @@ -0,0 +1,9 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + tools:context="org.libreoffice.MainActivity" > + <item android:id="@+id/action_settings" + android:title="@string/action_settings" + android:orderInCategory="100" + app:showAsAction="never" /> +</menu> diff --git a/android/experimental/LOAndroid2/app/src/main/res/values-w820dp/dimens.xml b/android/experimental/LOAndroid2/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 000000000000..63fc81644461 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ +<resources> + <!-- Example customization of dimensions originally defined in res/values/dimens.xml + (such as screen margins) for screens with more than 820dp of available width. This + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> + <dimen name="activity_horizontal_margin">64dp</dimen> +</resources> diff --git a/android/experimental/LOAndroid2/app/src/main/res/values/colors.xml b/android/experimental/LOAndroid2/app/src/main/res/values/colors.xml new file mode 100644 index 000000000000..f8e207d4e00e --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/values/colors.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<resources> + <color name="background_light">#FFECF0F3</color> + <color name="background_normal">#FFCED7DE</color> + <color name="background_private">#FF292C29</color> + <color name="background_tabs">#FF363B40</color> + <color name="highlight">#33000000</color> + <color name="highlight_focused">#1A000000</color> + <color name="highlight_dark">#33FFFFFF</color> + <color name="highlight_dark_focused">#1AFFFFFF</color> + + <!-- highlight on shaped button: 20% white over background_tabs --> + <color name="highlight_shaped">#FF696D71</color> + + <!-- highlight-focused on shaped button: 10% white over background_tabs --> + <color name="highlight_shaped_focused">#FF565B60</color> + + <!-- highlight on nav button: 20% black over background_normal --> + <color name="highlight_nav">#FFA5ACB2</color> + + <!-- highlight-focused on nav button: 10% black over background_normal --> + <color name="highlight_nav_focused">#FFB9C1C7</color> + + <!-- highlight on private nav button: 20% white over background_private --> + <color name="highlight_nav_pb">#FF545654</color> + + <!-- highlight-focused on private nav button: 10% white over background_private --> + <color name="highlight_nav_focused_pb">#FF3F423F</color> + + <!-- + Application theme colors + --> + <!-- Default colors --> + <color name="text_color_primary">#222222</color> + <color name="text_color_secondary">#777777</color> + <color name="text_color_tertiary">#9198A1</color> + + <!-- Default inverse colors --> + <color name="text_color_primary_inverse">#FFFFFF</color> + <color name="text_color_secondary_inverse">#DDDDDD</color> + <color name="text_color_tertiary_inverse">#A4A7A9</color> + + <!-- Disabled colors --> + <color name="text_color_primary_disable_only">#999999</color> + + <!-- Hint colors --> + <color name="text_color_hint">#666666</color> + <color name="text_color_hint_inverse">#7F828A</color> + + <!-- Highlight colors --> + <color name="text_color_highlight">#FF9500</color> + <color name="text_color_highlight_inverse">#D06BFF</color> + + <!-- Link colors --> + <color name="text_color_link">#22629E</color> + + <color name="splash_background">#000000</color> + <color name="splash_msgfont">#ffffff</color> + <color name="splash_urlfont">#000000</color> + <color name="splash_content">#ffffff</color> + + <color name="doorhanger_text">#FF222222</color> + <color name="doorhanger_link">#FF2AA1FE</color> + <color name="doorhanger_divider_light">#FFD1D5DA</color> + <color name="doorhanger_divider_dark">#FFB3C2CE</color> + <color name="doorhanger_background_dark">#FFDDE4EA</color> + + <color name="validation_message_text">#ffffff</color> + <color name="url_bar_text_highlight">#FFFF9500</color> + <color name="url_bar_text_highlight_pb">#FFD06BFF</color> + <color name="suggestion_primary">#dddddd</color> + <color name="suggestion_pressed">#bbbbbb</color> + <color name="tab_row_pressed">#4D000000</color> + <color name="dialogtitle_textcolor">#ffffff</color> + + <color name="textbox_background">#FFF</color> + <color name="textbox_background_disabled">#DDD</color> + <color name="textbox_stroke">#000</color> + <color name="textbox_stroke_disabled">#666</color> + + <color name="url_bar_urltext">#A6A6A6</color> + <color name="url_bar_domaintext">#000</color> + <color name="url_bar_domaintext_private">#FFF</color> + <color name="url_bar_blockedtext">#b14646</color> + <color name="url_bar_shadow">#12000000</color> + + <color name="home_last_tab_bar_bg">#FFF5F7F9</color> + + <color name="panel_grid_item_image_background">#D1D9E1</color> +</resources> + diff --git a/android/experimental/LOAndroid2/app/src/main/res/values/dimens.xml b/android/experimental/LOAndroid2/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000000..47c82246738c --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> diff --git a/android/experimental/LOAndroid2/app/src/main/res/values/strings.xml b/android/experimental/LOAndroid2/app/src/main/res/values/strings.xml new file mode 100644 index 000000000000..8864167560f8 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">LOAndroid</string> + <string name="hello_world">Hello world!</string> + <string name="action_settings">Settings</string> + +</resources> diff --git a/android/experimental/LOAndroid2/app/src/main/res/values/styles.xml b/android/experimental/LOAndroid2/app/src/main/res/values/styles.xml new file mode 100644 index 000000000000..ff6c9d2c0fb9 --- /dev/null +++ b/android/experimental/LOAndroid2/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> + <!-- Customize your theme here. --> + </style> + +</resources> diff --git a/android/experimental/LOAndroid2/build.gradle b/android/experimental/LOAndroid2/build.gradle new file mode 100644 index 000000000000..4c8f6d8b90ca --- /dev/null +++ b/android/experimental/LOAndroid2/build.gradle @@ -0,0 +1,16 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.11.+' + } +} + +allprojects { + repositories { + mavenCentral() + } +} diff --git a/android/experimental/LOAndroid2/gradle.properties b/android/experimental/LOAndroid2/gradle.properties new file mode 100644 index 000000000000..5d08ba75bb97 --- /dev/null +++ b/android/experimental/LOAndroid2/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Settings specified in this file will override any Gradle settings +# configured through the IDE. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true
\ No newline at end of file diff --git a/android/experimental/LOAndroid2/gradle/wrapper/gradle-wrapper.jar b/android/experimental/LOAndroid2/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 000000000000..8c0fb64a8698 --- /dev/null +++ b/android/experimental/LOAndroid2/gradle/wrapper/gradle-wrapper.jar diff --git a/android/experimental/LOAndroid2/gradle/wrapper/gradle-wrapper.properties b/android/experimental/LOAndroid2/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..2405fae37f2f --- /dev/null +++ b/android/experimental/LOAndroid2/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jun 09 10:23:20 CEST 2014 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip diff --git a/android/experimental/LOAndroid2/gradlew b/android/experimental/LOAndroid2/gradlew new file mode 100644 index 000000000000..91a7e269e19d --- /dev/null +++ b/android/experimental/LOAndroid2/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/android/experimental/LOAndroid2/gradlew.bat b/android/experimental/LOAndroid2/gradlew.bat new file mode 100644 index 000000000000..aec99730b4e8 --- /dev/null +++ b/android/experimental/LOAndroid2/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android/experimental/LOAndroid2/settings.gradle b/android/experimental/LOAndroid2/settings.gradle new file mode 100644 index 000000000000..e7b4def49cb5 --- /dev/null +++ b/android/experimental/LOAndroid2/settings.gradle @@ -0,0 +1 @@ +include ':app' |