summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/Command.cpp5
-rw-r--r--tools/aapt/Package.cpp2
-rw-r--r--tools/aapt/Resource.cpp9
-rw-r--r--tools/aapt/ResourceTable.cpp2
-rw-r--r--tools/aapt/StringPool.cpp2
-rw-r--r--tools/aapt/ZipFile.h2
-rw-r--r--tools/layoutlib/README4
-rw-r--r--tools/layoutlib/bridge/.classpath2
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Canvas.java40
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Matrix.java1032
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java1011
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Path.java40
-rw-r--r--tools/layoutlib/bridge/src/android/view/BridgeInflater.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java40
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java11
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java8
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/DelegateManager.java89
-rw-r--r--tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java42
-rw-r--r--tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestNativeDelegate.java115
-rw-r--r--tools/layoutlib/create/README.txt17
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java27
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java155
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java99
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java94
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java319
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java65
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java30
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java26
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java21
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java53
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java12
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java304
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java27
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java43
34 files changed, 2501 insertions, 1251 deletions
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index ad0465d..e9833c9 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -198,8 +198,10 @@ int doList(Bundle* bundle)
if (&res == NULL) {
printf("\nNo resource table found.\n");
} else {
+#ifndef HAVE_ANDROID_OS
printf("\nResource table:\n");
res.print(false);
+#endif
}
Asset* manifestAsset = assets.openNonAsset("AndroidManifest.xml",
@@ -428,8 +430,9 @@ int doDump(Bundle* bundle)
}
if (strcmp("resources", option) == 0) {
+#ifndef HAVE_ANDROID_OS
res.print(bundle->getValues());
-
+#endif
} else if (strcmp("xmltree", option) == 0) {
if (bundle->getFileSpecCount() < 3) {
fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index 999a5cf..3cb614f 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -441,7 +441,7 @@ ssize_t processJarFile(ZipFile* jar, ZipFile* out)
ssize_t processJarFiles(Bundle* bundle, ZipFile* zip)
{
- ssize_t err;
+ status_t err;
ssize_t count = 0;
const android::Vector<const char*>& jars = bundle->getJarFiles();
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index c8ba904..822262e 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -542,11 +542,11 @@ static bool applyFileOverlay(Bundle *bundle,
DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > baseFiles =
baseGroup->getFiles();
for (size_t i=0; i < baseFiles.size(); i++) {
- printf("baseFile %ld has flavor %s\n", i,
+ printf("baseFile %d has flavor %s\n", i,
baseFiles.keyAt(i).toString().string());
}
for (size_t i=0; i < overlayFiles.size(); i++) {
- printf("overlayFile %ld has flavor %s\n", i,
+ printf("overlayFile %d has flavor %s\n", i,
overlayFiles.keyAt(i).toString().string());
}
}
@@ -560,7 +560,7 @@ static bool applyFileOverlay(Bundle *bundle,
keyAt(overlayGroupIndex));
if(baseFileIndex < UNKNOWN_ERROR) {
if (bundle->getVerbose()) {
- printf("found a match (%ld) for overlay file %s, for flavor %s\n",
+ printf("found a match (%d) for overlay file %s, for flavor %s\n",
baseFileIndex,
overlayGroup->getLeaf().string(),
overlayFiles.keyAt(overlayGroupIndex).toString().string());
@@ -2083,12 +2083,13 @@ writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
// tag:attribute pairs that should be checked in layout files.
KeyedVector<String8, NamespaceAttributePair> kLayoutTagAttrPairs;
addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, "class");
+ addTagAttrPair(&kLayoutTagAttrPairs, "fragment", NULL, "class");
addTagAttrPair(&kLayoutTagAttrPairs, "fragment", RESOURCES_ANDROID_NAMESPACE, "name");
// tag:attribute pairs that should be checked in xml files.
KeyedVector<String8, NamespaceAttributePair> kXmlTagAttrPairs;
addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment");
- addTagAttrPair(&kXmlTagAttrPairs, "Header", RESOURCES_ANDROID_NAMESPACE, "fragment");
+ addTagAttrPair(&kXmlTagAttrPairs, "header", RESOURCES_ANDROID_NAMESPACE, "fragment");
const Vector<sp<AaptDir> >& dirs = assets->resDirs();
const size_t K = dirs.size();
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 29644a6..1d6b18d 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2442,7 +2442,7 @@ ResourceTable::validateLocalizations(void)
if (configSet.count(defaultLocale) == 0) {
fprintf(stdout, "aapt: warning: string '%s' has no default translation in %s; found:",
String8(nameIter->first).string(), mBundle->getResourceSourceDirs()[0]);
- for (set<String8>::iterator locales = configSet.begin();
+ for (set<String8>::const_iterator locales = configSet.begin();
locales != configSet.end();
locales++) {
fprintf(stdout, " %s", (*locales).string());
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index a09cec0..e28bdff 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -30,7 +30,7 @@ void printStringPool(const ResStringPool* pool)
str = String8(pool->stringAt(s, &len)).string();
}
- printf("String #%ld: %s\n", s, str);
+ printf("String #%d: %s\n", s, str);
}
}
diff --git a/tools/aapt/ZipFile.h b/tools/aapt/ZipFile.h
index dbbd072..7877550 100644
--- a/tools/aapt/ZipFile.h
+++ b/tools/aapt/ZipFile.h
@@ -57,7 +57,7 @@ public:
/*
* Open a new or existing archive.
*/
- typedef enum {
+ enum {
kOpenReadOnly = 0x01,
kOpenReadWrite = 0x02,
kOpenCreate = 0x04, // create if it doesn't exist
diff --git a/tools/layoutlib/README b/tools/layoutlib/README
new file mode 100644
index 0000000..0fea9bd
--- /dev/null
+++ b/tools/layoutlib/README
@@ -0,0 +1,4 @@
+Layoutlib is a custom version of the android View framework designed to run inside Eclipse.
+The goal of the library is to provide layout rendering in Eclipse that are very very close to their rendering on devices.
+
+None of the com.android.* or android.* classes in layoutlib run on devices. \ No newline at end of file
diff --git a/tools/layoutlib/bridge/.classpath b/tools/layoutlib/bridge/.classpath
index 70140d8..d3bcb6c 100644
--- a/tools/layoutlib/bridge/.classpath
+++ b/tools/layoutlib/bridge/.classpath
@@ -6,7 +6,7 @@
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/layoutlib_api/layoutlib_api-prebuilt.jar"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
- <classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/layoutlib.jar" sourcepath="/ANDROID_SRC/frameworks/base/core/java"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_OUT_FRAMEWORK/layoutlib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/>
<classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/ninepatch.jar" sourcepath="/ANDROID_SRC/development/tools/ninepatch/src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
index d5d315e..0dccc0d6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -18,13 +18,6 @@ package android.graphics;
import com.android.layoutlib.api.ILayoutLog;
-import android.graphics.DrawFilter;
-import android.graphics.Picture;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.Xfermode;
import android.graphics.Paint.Align;
import android.graphics.Paint.FontInfo;
import android.graphics.Paint.Style;
@@ -42,8 +35,6 @@ import java.awt.image.BufferedImage;
import java.util.List;
import java.util.Stack;
-import javax.microedition.khronos.opengles.GL;
-
/**
* Re-implementation of the Canvas, 100% in java on top of a BufferedImage.
*/
@@ -69,11 +60,6 @@ public class Canvas extends _Original_Canvas {
throw new UnsupportedOperationException("Can't create Canvas(int)");
}
- public Canvas(javax.microedition.khronos.opengles.GL gl) {
- mLogger = null;
- throw new UnsupportedOperationException("Can't create Canvas(javax.microedition.khronos.opengles.GL)");
- }
-
// custom constructors for our use.
public Canvas(int width, int height, ILayoutLog logger) {
mLogger = logger;
@@ -514,7 +500,7 @@ public class Canvas extends _Original_Canvas {
// get the Graphics2D current matrix
AffineTransform currentTx = g.getTransform();
// get the AffineTransform from the matrix
- AffineTransform matrixTx = matrix.getTransform();
+ AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(matrix);
// combine them so that the matrix is applied after.
currentTx.preConcatenate(matrixTx);
@@ -974,9 +960,9 @@ public class Canvas extends _Original_Canvas {
Graphics2D g = getGraphics2d();
// and apply the matrix
- g.setTransform(matrix.getTransform());
+ g.setTransform(Matrix_Delegate.getAffineTransform(matrix));
- if (mLogger != null && matrix.hasPerspective()) {
+ if (mLogger != null && Matrix_Delegate.hasPerspective(matrix)) {
mLogger.warning("android.graphics.Canvas#setMatrix(android.graphics.Matrix) only supports affine transformations in the Layout Editor.");
}
}
@@ -992,7 +978,7 @@ public class Canvas extends _Original_Canvas {
// get its current matrix
AffineTransform currentTx = g.getTransform();
// get the AffineTransform of the given matrix
- AffineTransform matrixTx = matrix.getTransform();
+ AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(matrix);
// combine them so that the given matrix is applied after.
currentTx.preConcatenate(matrixTx);
@@ -1174,15 +1160,6 @@ public class Canvas extends _Original_Canvas {
}
/* (non-Javadoc)
- * @see android.graphics.Canvas#getGL()
- */
- @Override
- public GL getGL() {
- // TODO Auto-generated method stub
- return super.getGL();
- }
-
- /* (non-Javadoc)
* @see android.graphics.Canvas#getMatrix()
*/
@Override
@@ -1257,15 +1234,6 @@ public class Canvas extends _Original_Canvas {
}
/* (non-Javadoc)
- * @see android.graphics.Canvas#setViewport(int, int)
- */
- @Override
- public void setViewport(int width, int height) {
- // TODO Auto-generated method stub
- super.setViewport(width, height);
- }
-
- /* (non-Javadoc)
* @see android.graphics.Canvas#skew(float, float)
*/
@Override
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix.java b/tools/layoutlib/bridge/src/android/graphics/Matrix.java
deleted file mode 100644
index 9e30671..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix.java
+++ /dev/null
@@ -1,1032 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import java.awt.geom.AffineTransform;
-import java.awt.geom.NoninvertibleTransformException;
-
-
-/**
- * A matrix implementation overridden by the LayoutLib bridge.
- */
-public class Matrix extends _Original_Matrix {
-
- float mValues[] = new float[9];
-
- /**
- * Create an identity matrix
- */
- public Matrix() {
- reset();
- }
-
- /**
- * Create a matrix that is a (deep) copy of src
- * @param src The matrix to copy into this matrix
- */
- public Matrix(Matrix src) {
- set(src);
- }
-
- /**
- * Creates a Matrix object from the float array. The array becomes the internal storage
- * of the object.
- * @param data
- */
- private Matrix(float[] data) {
- assert data.length != 9;
- mValues = data;
- }
-
- //---------- Custom Methods
-
- /**
- * Adds the given transformation to the current Matrix
- * <p/>This in effect does this = this*matrix
- * @param matrix
- */
- private void addTransform(float[] matrix) {
- float[] tmp = new float[9];
-
- // first row
- tmp[0] = matrix[0] * mValues[0] + matrix[1] * mValues[3] + matrix[2] * mValues[6];
- tmp[1] = matrix[0] * mValues[1] + matrix[1] * mValues[4] + matrix[2] * mValues[7];
- tmp[2] = matrix[0] * mValues[2] + matrix[1] * mValues[5] + matrix[2] * mValues[8];
-
- // 2nd row
- tmp[3] = matrix[3] * mValues[0] + matrix[4] * mValues[3] + matrix[5] * mValues[6];
- tmp[4] = matrix[3] * mValues[1] + matrix[4] * mValues[4] + matrix[5] * mValues[7];
- tmp[5] = matrix[3] * mValues[2] + matrix[4] * mValues[5] + matrix[5] * mValues[8];
-
- // 3rd row
- tmp[6] = matrix[6] * mValues[0] + matrix[7] * mValues[3] + matrix[8] * mValues[6];
- tmp[7] = matrix[6] * mValues[1] + matrix[7] * mValues[4] + matrix[8] * mValues[7];
- tmp[8] = matrix[6] * mValues[2] + matrix[7] * mValues[5] + matrix[8] * mValues[8];
-
- // copy the result over to mValues
- mValues = tmp;
- }
-
- public AffineTransform getTransform() {
- // the AffineTransform constructor takes the value in a different order
- // for a matrix [ 0 1 2 ]
- // [ 3 4 5 ]
- // the order is 0, 3, 1, 4, 2, 5...
- return new AffineTransform(mValues[0], mValues[3], mValues[1],
- mValues[4], mValues[2], mValues[5]);
- }
-
- public boolean hasPerspective() {
- return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1);
- }
-
- //----------
-
- /**
- * Returns true if the matrix is identity.
- * This maybe faster than testing if (getType() == 0)
- */
- @Override
- public boolean isIdentity() {
- for (int i = 0, k = 0; i < 3; i++) {
- for (int j = 0; j < 3; j++, k++) {
- if (mValues[k] != ((i==j) ? 1 : 0)) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- /**
- * Returns true if will map a rectangle to another rectangle. This can be
- * true if the matrix is identity, scale-only, or rotates a multiple of 90
- * degrees.
- */
- @Override
- public boolean rectStaysRect() {
- return (computeTypeMask() & kRectStaysRect_Mask) != 0;
- }
-
- /**
- * (deep) copy the src matrix into this matrix. If src is null, reset this
- * matrix to the identity matrix.
- */
- public void set(Matrix src) {
- if (src == null) {
- reset();
- } else {
- System.arraycopy(src.mValues, 0, mValues, 0, mValues.length);
- }
- }
-
- @Override
- public void set(_Original_Matrix src) {
- throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
- }
-
- /** Returns true if obj is a Matrix and its values equal our values.
- */
- @Override
- public boolean equals(Object obj) {
- if (obj != null && obj instanceof Matrix) {
- Matrix matrix = (Matrix)obj;
- for (int i = 0 ; i < 9 ; i++) {
- if (mValues[i] != matrix.mValues[i]) {
- return false;
- }
- }
-
- return true;
- }
-
- return false;
- }
-
- /** Set the matrix to identity */
- @Override
- public void reset() {
- for (int i = 0, k = 0; i < 3; i++) {
- for (int j = 0; j < 3; j++, k++) {
- mValues[k] = ((i==j) ? 1 : 0);
- }
- }
- }
-
- /** Set the matrix to translate by (dx, dy). */
- @Override
- public void setTranslate(float dx, float dy) {
- mValues[0] = 1;
- mValues[1] = 0;
- mValues[2] = dx;
- mValues[3] = 0;
- mValues[4] = 1;
- mValues[5] = dy;
- mValues[6] = 0;
- mValues[7] = 0;
- mValues[8] = 1;
- }
-
- /**
- * Set the matrix to scale by sx and sy, with a pivot point at (px, py).
- * The pivot point is the coordinate that should remain unchanged by the
- * specified transformation.
- */
- @Override
- public void setScale(float sx, float sy, float px, float py) {
- // TODO: do it in one pass
-
- // translate so that the pivot is in 0,0
- mValues[0] = 1;
- mValues[1] = 0;
- mValues[2] = -px;
- mValues[3] = 0;
- mValues[4] = 1;
- mValues[5] = -py;
- mValues[6] = 0;
- mValues[7] = 0;
- mValues[8] = 1;
-
- // scale
- addTransform(new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 });
- // translate back the pivot
- addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 });
- }
-
- /** Set the matrix to scale by sx and sy. */
- @Override
- public void setScale(float sx, float sy) {
- mValues[0] = sx;
- mValues[1] = 0;
- mValues[2] = 0;
- mValues[3] = 0;
- mValues[4] = sy;
- mValues[5] = 0;
- mValues[6] = 0;
- mValues[7] = 0;
- mValues[8] = 1;
- }
-
- /**
- * Set the matrix to rotate by the specified number of degrees, with a pivot
- * point at (px, py). The pivot point is the coordinate that should remain
- * unchanged by the specified transformation.
- */
- @Override
- public void setRotate(float degrees, float px, float py) {
- // TODO: do it in one pass
-
- // translate so that the pivot is in 0,0
- mValues[0] = 1;
- mValues[1] = 0;
- mValues[2] = -px;
- mValues[3] = 0;
- mValues[4] = 1;
- mValues[5] = -py;
- mValues[6] = 0;
- mValues[7] = 0;
- mValues[8] = 1;
-
- // scale
- double rad = Math.toRadians(degrees);
- float cos = (float)Math.cos(rad);
- float sin = (float)Math.sin(rad);
- addTransform(new float[] { cos, -sin, 0, sin, cos, 0, 0, 0, 1 });
- // translate back the pivot
- addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 });
- }
-
- /**
- * Set the matrix to rotate about (0,0) by the specified number of degrees.
- */
- @Override
- public void setRotate(float degrees) {
- double rad = Math.toRadians(degrees);
- float cos = (float)Math.cos(rad);
- float sin = (float)Math.sin(rad);
-
- mValues[0] = cos;
- mValues[1] = -sin;
- mValues[2] = 0;
- mValues[3] = sin;
- mValues[4] = cos;
- mValues[5] = 0;
- mValues[6] = 0;
- mValues[7] = 0;
- mValues[8] = 1;
- }
-
- /**
- * Set the matrix to rotate by the specified sine and cosine values, with a
- * pivot point at (px, py). The pivot point is the coordinate that should
- * remain unchanged by the specified transformation.
- */
- @Override
- public void setSinCos(float sinValue, float cosValue, float px, float py) {
- // TODO: do it in one pass
-
- // translate so that the pivot is in 0,0
- mValues[0] = 1;
- mValues[1] = 0;
- mValues[2] = -px;
- mValues[3] = 0;
- mValues[4] = 1;
- mValues[5] = -py;
- mValues[6] = 0;
- mValues[7] = 0;
- mValues[8] = 1;
-
- // scale
- addTransform(new float[] { cosValue, -sinValue, 0, sinValue, cosValue, 0, 0, 0, 1 });
- // translate back the pivot
- addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 });
- }
-
- /** Set the matrix to rotate by the specified sine and cosine values. */
- @Override
- public void setSinCos(float sinValue, float cosValue) {
- mValues[0] = cosValue;
- mValues[1] = -sinValue;
- mValues[2] = 0;
- mValues[3] = sinValue;
- mValues[4] = cosValue;
- mValues[5] = 0;
- mValues[6] = 0;
- mValues[7] = 0;
- mValues[8] = 1;
- }
-
- /**
- * Set the matrix to skew by sx and sy, with a pivot point at (px, py).
- * The pivot point is the coordinate that should remain unchanged by the
- * specified transformation.
- */
- @Override
- public void setSkew(float kx, float ky, float px, float py) {
- // TODO: do it in one pass
-
- // translate so that the pivot is in 0,0
- mValues[0] = 1;
- mValues[1] = 0;
- mValues[2] = -px;
- mValues[3] = 0;
- mValues[4] = 1;
- mValues[5] = -py;
- mValues[6] = 0;
- mValues[7] = 0;
- mValues[8] = 1;
-
- // scale
- addTransform(new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 });
- // translate back the pivot
- addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 });
- }
-
- /** Set the matrix to skew by sx and sy. */
- @Override
- public void setSkew(float kx, float ky) {
- mValues[0] = 1;
- mValues[1] = kx;
- mValues[2] = -0;
- mValues[3] = ky;
- mValues[4] = 1;
- mValues[5] = 0;
- mValues[6] = 0;
- mValues[7] = 0;
- mValues[8] = 1;
- }
-
- /**
- * Set the matrix to the concatenation of the two specified matrices,
- * returning true if the the result can be represented. Either of the two
- * matrices may also be the target matrix. this = a * b
- */
- public boolean setConcat(Matrix a, Matrix b) {
- if (a == this) {
- preConcat(b);
- } else if (b == this) {
- postConcat(b);
- } else {
- Matrix tmp = new Matrix(b);
- tmp.addTransform(a.mValues);
- set(tmp);
- }
-
- return true;
- }
-
- @Override
- public boolean setConcat(_Original_Matrix a, _Original_Matrix b) {
- throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
- }
-
- /**
- * Preconcats the matrix with the specified translation.
- * M' = M * T(dx, dy)
- */
- @Override
- public boolean preTranslate(float dx, float dy) {
- // create a matrix that will be multiply by this
- Matrix m = new Matrix(new float[] { 1, 0, dx, 0, 1, dy, 0, 0, 1 });
- m.addTransform(this.mValues);
-
- System.arraycopy(m.mValues, 0, mValues, 0, 9);
- return true;
- }
-
- /**
- * Preconcats the matrix with the specified scale.
- * M' = M * S(sx, sy, px, py)
- */
- @Override
- public boolean preScale(float sx, float sy, float px, float py) {
- Matrix m = new Matrix();
- m.setScale(sx, sy, px, py);
- m.addTransform(mValues);
- set(m);
-
- return true;
- }
-
- /**
- * Preconcats the matrix with the specified scale.
- * M' = M * S(sx, sy)
- */
- @Override
- public boolean preScale(float sx, float sy) {
- Matrix m = new Matrix();
- m.setScale(sx, sy);
- m.addTransform(mValues);
- set(m);
-
- return true;
- }
-
- /**
- * Preconcats the matrix with the specified rotation.
- * M' = M * R(degrees, px, py)
- */
- @Override
- public boolean preRotate(float degrees, float px, float py) {
- Matrix m = new Matrix();
- m.setRotate(degrees, px, py);
- m.addTransform(mValues);
- set(m);
-
- return true;
- }
-
- /**
- * Preconcats the matrix with the specified rotation.
- * M' = M * R(degrees)
- */
- @Override
- public boolean preRotate(float degrees) {
- Matrix m = new Matrix();
- m.setRotate(degrees);
- m.addTransform(mValues);
- set(m);
-
- return true;
- }
-
- /**
- * Preconcats the matrix with the specified skew.
- * M' = M * K(kx, ky, px, py)
- */
- @Override
- public boolean preSkew(float kx, float ky, float px, float py) {
- Matrix m = new Matrix();
- m.setSkew(kx, ky, px, py);
- m.addTransform(mValues);
- set(m);
-
- return true;
- }
-
- /**
- * Preconcats the matrix with the specified skew.
- * M' = M * K(kx, ky)
- */
- @Override
- public boolean preSkew(float kx, float ky) {
- Matrix m = new Matrix();
- m.setSkew(kx, ky);
- m.addTransform(mValues);
- set(m);
-
- return true;
- }
-
- /**
- * Preconcats the matrix with the specified matrix.
- * M' = M * other
- */
- public boolean preConcat(Matrix other) {
- Matrix m = new Matrix(other);
- other.addTransform(mValues);
- set(m);
-
- return true;
- }
-
- @Override
- public boolean preConcat(_Original_Matrix other) {
- throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
- }
-
- /**
- * Postconcats the matrix with the specified translation.
- * M' = T(dx, dy) * M
- */
- @Override
- public boolean postTranslate(float dx, float dy) {
- addTransform(new float[] { 1, 0, dx, 0, 1, dy, 0, 0, 1 });
- return true;
- }
-
- /**
- * Postconcats the matrix with the specified scale.
- * M' = S(sx, sy, px, py) * M
- */
- @Override
- public boolean postScale(float sx, float sy, float px, float py) {
- // TODO: do it in one pass
- // translate so that the pivot is in 0,0
- addTransform(new float[] { 1, 0, -px, 0, 1, py, 0, 0, 1 });
- // scale
- addTransform(new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 });
- // translate back the pivot
- addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 });
-
- return true;
- }
-
- /**
- * Postconcats the matrix with the specified scale.
- * M' = S(sx, sy) * M
- */
- @Override
- public boolean postScale(float sx, float sy) {
- addTransform(new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 });
- return true;
- }
-
- /**
- * Postconcats the matrix with the specified rotation.
- * M' = R(degrees, px, py) * M
- */
- @Override
- public boolean postRotate(float degrees, float px, float py) {
- // TODO: do it in one pass
- // translate so that the pivot is in 0,0
- addTransform(new float[] { 1, 0, -px, 0, 1, py, 0, 0, 1 });
- // scale
- double rad = Math.toRadians(degrees);
- float cos = (float)Math.cos(rad);
- float sin = (float)Math.sin(rad);
- addTransform(new float[] { cos, -sin, 0, sin, cos, 0, 0, 0, 1 });
- // translate back the pivot
- addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 });
-
- return true;
- }
-
- /**
- * Postconcats the matrix with the specified rotation.
- * M' = R(degrees) * M
- */
- @Override
- public boolean postRotate(float degrees) {
- double rad = Math.toRadians(degrees);
- float cos = (float)Math.cos(rad);
- float sin = (float)Math.sin(rad);
- addTransform(new float[] { cos, -sin, 0, sin, cos, 0, 0, 0, 1 });
-
- return true;
- }
-
- /**
- * Postconcats the matrix with the specified skew.
- * M' = K(kx, ky, px, py) * M
- */
- @Override
- public boolean postSkew(float kx, float ky, float px, float py) {
- // TODO: do it in one pass
- // translate so that the pivot is in 0,0
- addTransform(new float[] { 1, 0, -px, 0, 1, py, 0, 0, 1 });
- // scale
- addTransform(new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 });
- // translate back the pivot
- addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 });
-
- return true;
- }
-
- /**
- * Postconcats the matrix with the specified skew.
- * M' = K(kx, ky) * M
- */
- @Override
- public boolean postSkew(float kx, float ky) {
- addTransform(new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 });
-
- return true;
- }
-
- /**
- * Postconcats the matrix with the specified matrix.
- * M' = other * M
- */
- public boolean postConcat(Matrix other) {
- addTransform(other.mValues);
-
- return true;
- }
-
- @Override
- public boolean postConcat(_Original_Matrix other) {
- throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
- }
-
- /** Controlls how the src rect should align into the dst rect for
- setRectToRect().
- */
- public enum ScaleToFit {
- /**
- * Scale in X and Y independently, so that src matches dst exactly.
- * This may change the aspect ratio of the src.
- */
- FILL (0),
- /**
- * Compute a scale that will maintain the original src aspect ratio,
- * but will also ensure that src fits entirely inside dst. At least one
- * axis (X or Y) will fit exactly. START aligns the result to the
- * left and top edges of dst.
- */
- START (1),
- /**
- * Compute a scale that will maintain the original src aspect ratio,
- * but will also ensure that src fits entirely inside dst. At least one
- * axis (X or Y) will fit exactly. The result is centered inside dst.
- */
- CENTER (2),
- /**
- * Compute a scale that will maintain the original src aspect ratio,
- * but will also ensure that src fits entirely inside dst. At least one
- * axis (X or Y) will fit exactly. END aligns the result to the
- * right and bottom edges of dst.
- */
- END (3);
-
- // the native values must match those in SkMatrix.h
- ScaleToFit(int nativeInt) {
- this.nativeInt = nativeInt;
- }
- final int nativeInt;
- }
-
- /**
- * Set the matrix to the scale and translate values that map the source
- * rectangle to the destination rectangle, returning true if the result
- * can be represented.
- *
- * @param src the source rectangle to map from.
- * @param dst the destination rectangle to map to.
- * @param stf the ScaleToFit option
- * @return true if the matrix can be represented by the rectangle mapping.
- */
- public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
- if (dst == null || src == null) {
- throw new NullPointerException();
- }
-
- if (src.isEmpty()) {
- reset();
- return false;
- }
-
- if (dst.isEmpty()) {
- mValues[0] = mValues[1] = mValues[2] = mValues[3] = mValues[4] = mValues[5]
- = mValues[6] = mValues[7] = 0;
- mValues[8] = 1;
- } else {
- float tx, sx = dst.width() / src.width();
- float ty, sy = dst.height() / src.height();
- boolean xLarger = false;
-
- if (stf != ScaleToFit.FILL) {
- if (sx > sy) {
- xLarger = true;
- sx = sy;
- } else {
- sy = sx;
- }
- }
-
- tx = dst.left - src.left * sx;
- ty = dst.top - src.top * sy;
- if (stf == ScaleToFit.CENTER || stf == ScaleToFit.END) {
- float diff;
-
- if (xLarger) {
- diff = dst.width() - src.width() * sy;
- } else {
- diff = dst.height() - src.height() * sy;
- }
-
- if (stf == ScaleToFit.CENTER) {
- diff = diff / 2;
- }
-
- if (xLarger) {
- tx += diff;
- } else {
- ty += diff;
- }
- }
-
- mValues[0] = sx;
- mValues[4] = sy;
- mValues[2] = tx;
- mValues[5] = ty;
- mValues[1] = mValues[3] = mValues[6] = mValues[7] = 0;
-
- }
- // shared cleanup
- mValues[8] = 1;
- return true;
- }
-
- @Override
- public boolean setRectToRect(RectF src, RectF dst, _Original_Matrix.ScaleToFit stf) {
- throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
- }
-
- /**
- * Set the matrix such that the specified src points would map to the
- * specified dst points. The "points" are represented as an array of floats,
- * order [x0, y0, x1, y1, ...], where each "point" is 2 float values.
- *
- * @param src The array of src [x,y] pairs (points)
- * @param srcIndex Index of the first pair of src values
- * @param dst The array of dst [x,y] pairs (points)
- * @param dstIndex Index of the first pair of dst values
- * @param pointCount The number of pairs/points to be used. Must be [0..4]
- * @return true if the matrix was set to the specified transformation
- */
- @Override
- public boolean setPolyToPoly(float[] src, int srcIndex,
- float[] dst, int dstIndex,
- int pointCount) {
- if (pointCount > 4) {
- throw new IllegalArgumentException();
- }
- checkPointArrays(src, srcIndex, dst, dstIndex, pointCount);
- throw new UnsupportedOperationException("STUB NEEDED");
- }
-
- /**
- * If this matrix can be inverted, return true and if inverse is not null,
- * set inverse to be the inverse of this matrix. If this matrix cannot be
- * inverted, ignore inverse and return false.
- */
- public boolean invert(Matrix inverse) {
- if (inverse == null) {
- return false;
- }
-
- try {
- AffineTransform affineTransform = getTransform();
- AffineTransform inverseTransform = affineTransform.createInverse();
- inverse.mValues[0] = (float)inverseTransform.getScaleX();
- inverse.mValues[1] = (float)inverseTransform.getShearX();
- inverse.mValues[2] = (float)inverseTransform.getTranslateX();
- inverse.mValues[3] = (float)inverseTransform.getScaleX();
- inverse.mValues[4] = (float)inverseTransform.getShearY();
- inverse.mValues[5] = (float)inverseTransform.getTranslateY();
-
- return true;
- } catch (NoninvertibleTransformException e) {
- return false;
- }
- }
-
- @Override
- public boolean invert(_Original_Matrix inverse) {
- throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
- }
-
- /**
- * Apply this matrix to the array of 2D points specified by src, and write
- * the transformed points into the array of points specified by dst. The
- * two arrays represent their "points" as pairs of floats [x, y].
- *
- * @param dst The array of dst points (x,y pairs)
- * @param dstIndex The index of the first [x,y] pair of dst floats
- * @param src The array of src points (x,y pairs)
- * @param srcIndex The index of the first [x,y] pair of src floats
- * @param pointCount The number of points (x,y pairs) to transform
- */
- @Override
- public void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,
- int pointCount) {
- checkPointArrays(src, srcIndex, dst, dstIndex, pointCount);
-
- for (int i = 0 ; i < pointCount ; i++) {
- // just in case we are doing in place, we better put this in temp vars
- float x = mValues[0] * src[i + srcIndex] +
- mValues[1] * src[i + srcIndex + 1] +
- mValues[2];
- float y = mValues[3] * src[i + srcIndex] +
- mValues[4] * src[i + srcIndex + 1] +
- mValues[5];
-
- dst[i + dstIndex] = x;
- dst[i + dstIndex + 1] = y;
- }
- }
-
- /**
- * Apply this matrix to the array of 2D vectors specified by src, and write
- * the transformed vectors into the array of vectors specified by dst. The
- * two arrays represent their "vectors" as pairs of floats [x, y].
- *
- * @param dst The array of dst vectors (x,y pairs)
- * @param dstIndex The index of the first [x,y] pair of dst floats
- * @param src The array of src vectors (x,y pairs)
- * @param srcIndex The index of the first [x,y] pair of src floats
- * @param vectorCount The number of vectors (x,y pairs) to transform
- */
- @Override
- public void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex,
- int vectorCount) {
- checkPointArrays(src, srcIndex, dst, dstIndex, vectorCount);
- throw new UnsupportedOperationException("STUB NEEDED");
- }
-
- /**
- * Apply this matrix to the array of 2D points specified by src, and write
- * the transformed points into the array of points specified by dst. The
- * two arrays represent their "points" as pairs of floats [x, y].
- *
- * @param dst The array of dst points (x,y pairs)
- * @param src The array of src points (x,y pairs)
- */
- @Override
- public void mapPoints(float[] dst, float[] src) {
- if (dst.length != src.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
- mapPoints(dst, 0, src, 0, dst.length >> 1);
- }
-
- /**
- * Apply this matrix to the array of 2D vectors specified by src, and write
- * the transformed vectors into the array of vectors specified by dst. The
- * two arrays represent their "vectors" as pairs of floats [x, y].
- *
- * @param dst The array of dst vectors (x,y pairs)
- * @param src The array of src vectors (x,y pairs)
- */
- @Override
- public void mapVectors(float[] dst, float[] src) {
- if (dst.length != src.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
- mapVectors(dst, 0, src, 0, dst.length >> 1);
- }
-
- /**
- * Apply this matrix to the array of 2D points, and write the transformed
- * points back into the array
- *
- * @param pts The array [x0, y0, x1, y1, ...] of points to transform.
- */
- @Override
- public void mapPoints(float[] pts) {
- mapPoints(pts, 0, pts, 0, pts.length >> 1);
- }
-
- /**
- * Apply this matrix to the array of 2D vectors, and write the transformed
- * vectors back into the array.
- * @param vecs The array [x0, y0, x1, y1, ...] of vectors to transform.
- */
- @Override
- public void mapVectors(float[] vecs) {
- mapVectors(vecs, 0, vecs, 0, vecs.length >> 1);
- }
-
- /**
- * Apply this matrix to the src rectangle, and write the transformed
- * rectangle into dst. This is accomplished by transforming the 4 corners of
- * src, and then setting dst to the bounds of those points.
- *
- * @param dst Where the transformed rectangle is written.
- * @param src The original rectangle to be transformed.
- * @return the result of calling rectStaysRect()
- */
- @Override
- public boolean mapRect(RectF dst, RectF src) {
- if (dst == null || src == null) {
- throw new NullPointerException();
- }
-
- // array with 4 corners
- float[] corners = new float[] {
- src.left, src.top,
- src.right, src.top,
- src.right, src.bottom,
- src.left, src.bottom,
- };
-
- // apply the transform to them.
- mapPoints(corners);
-
- // now put the result in the rect. We take the min/max of Xs and min/max of Ys
- dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
- dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
-
- dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
- dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
-
- return rectStaysRect();
- }
-
- /**
- * Apply this matrix to the rectangle, and write the transformed rectangle
- * back into it. This is accomplished by transforming the 4 corners of rect,
- * and then setting it to the bounds of those points
- *
- * @param rect The rectangle to transform.
- * @return the result of calling rectStaysRect()
- */
- @Override
- public boolean mapRect(RectF rect) {
- return mapRect(rect, rect);
- }
-
- /**
- * Return the mean radius of a circle after it has been mapped by
- * this matrix. NOTE: in perspective this value assumes the circle
- * has its center at the origin.
- */
- @Override
- public float mapRadius(float radius) {
- throw new UnsupportedOperationException("STUB NEEDED");
- }
-
- /** Copy 9 values from the matrix into the array.
- */
- @Override
- public void getValues(float[] values) {
- if (values.length < 9) {
- throw new ArrayIndexOutOfBoundsException();
- }
- System.arraycopy(mValues, 0, values, 0, mValues.length);
- }
-
- /** Copy 9 values from the array into the matrix.
- Depending on the implementation of Matrix, these may be
- transformed into 16.16 integers in the Matrix, such that
- a subsequent call to getValues() will not yield exactly
- the same values.
- */
- @Override
- public void setValues(float[] values) {
- if (values.length < 9) {
- throw new ArrayIndexOutOfBoundsException();
- }
- System.arraycopy(values, 0, mValues, 0, mValues.length);
- }
-
- @SuppressWarnings("unused")
- private final static int kIdentity_Mask = 0;
- private final static int kTranslate_Mask = 0x01; //!< set if the matrix has translation
- private final static int kScale_Mask = 0x02; //!< set if the matrix has X or Y scale
- private final static int kAffine_Mask = 0x04; //!< set if the matrix skews or rotates
- private final static int kPerspective_Mask = 0x08; //!< set if the matrix is in perspective
- private final static int kRectStaysRect_Mask = 0x10;
- @SuppressWarnings("unused")
- private final static int kUnknown_Mask = 0x80;
-
- @SuppressWarnings("unused")
- private final static int kAllMasks = kTranslate_Mask |
- kScale_Mask |
- kAffine_Mask |
- kPerspective_Mask |
- kRectStaysRect_Mask;
-
- // these guys align with the masks, so we can compute a mask from a variable 0/1
- @SuppressWarnings("unused")
- private final static int kTranslate_Shift = 0;
- @SuppressWarnings("unused")
- private final static int kScale_Shift = 1;
- @SuppressWarnings("unused")
- private final static int kAffine_Shift = 2;
- @SuppressWarnings("unused")
- private final static int kPerspective_Shift = 3;
- private final static int kRectStaysRect_Shift = 4;
-
- private int computeTypeMask() {
- int mask = 0;
-
- if (mValues[6] != 0. || mValues[7] != 0. || mValues[8] != 1.) {
- mask |= kPerspective_Mask;
- }
-
- if (mValues[2] != 0. || mValues[5] != 0.) {
- mask |= kTranslate_Mask;
- }
-
- float m00 = mValues[0];
- float m01 = mValues[1];
- float m10 = mValues[3];
- float m11 = mValues[4];
-
- if (m01 != 0. || m10 != 0.) {
- mask |= kAffine_Mask;
- }
-
- if (m00 != 1. || m11 != 1.) {
- mask |= kScale_Mask;
- }
-
- if ((mask & kPerspective_Mask) == 0) {
- // map non-zero to 1
- int im00 = m00 != 0 ? 1 : 0;
- int im01 = m01 != 0 ? 1 : 0;
- int im10 = m10 != 0 ? 1 : 0;
- int im11 = m11 != 0 ? 1 : 0;
-
- // record if the (p)rimary and (s)econdary diagonals are all 0 or
- // all non-zero (answer is 0 or 1)
- int dp0 = (im00 | im11) ^ 1; // true if both are 0
- int dp1 = im00 & im11; // true if both are 1
- int ds0 = (im01 | im10) ^ 1; // true if both are 0
- int ds1 = im01 & im10; // true if both are 1
-
- // return 1 if primary is 1 and secondary is 0 or
- // primary is 0 and secondary is 1
- mask |= ((dp0 & ds1) | (dp1 & ds0)) << kRectStaysRect_Shift;
- }
-
- return mask;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
new file mode 100644
index 0000000..ed2eff2
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
@@ -0,0 +1,1011 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+
+import com.android.layoutlib.bridge.DelegateManager;
+
+import android.graphics.Matrix.ScaleToFit;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Matrix
+ *
+ * Through the layoutlib_create tool, the original native methods of Matrix have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Matrix class.
+ *
+ * @see DelegateManager
+ *
+ */
+public final class Matrix_Delegate {
+
+ private final static int MATRIX_SIZE = 9;
+
+ // ---- delegate manager ----
+ private static final DelegateManager<Matrix_Delegate> sManager =
+ new DelegateManager<Matrix_Delegate>();
+
+ // ---- delegate data ----
+ private float mValues[] = new float[MATRIX_SIZE];
+
+ // ---- Public Helper methods ----
+
+ /**
+ * Returns an {@link AffineTransform} matching the given Matrix.
+ */
+ public static AffineTransform getAffineTransform(Matrix m) {
+ Matrix_Delegate delegate = sManager.getDelegate(m.native_instance);
+ if (delegate == null) {
+ assert false;
+ return null;
+ }
+
+ return getAffineTransform(delegate);
+ }
+
+ public static boolean hasPerspective(Matrix m) {
+ Matrix_Delegate delegate = sManager.getDelegate(m.native_instance);
+ if (delegate == null) {
+ assert false;
+ return false;
+ }
+
+ return (delegate.mValues[6] != 0 || delegate.mValues[7] != 0 || delegate.mValues[8] != 1);
+ }
+
+
+ // ---- native methods ----
+
+ public static int native_create(int native_src_or_zero) {
+ // create the delegate
+ Matrix_Delegate newDelegate = new Matrix_Delegate();
+
+ // copy from values if needed.
+ if (native_src_or_zero > 0) {
+ Matrix_Delegate oldDelegate = sManager.getDelegate(native_src_or_zero);
+ if (oldDelegate != null) {
+ System.arraycopy(
+ oldDelegate.mValues, 0,
+ newDelegate.mValues, 0,
+ MATRIX_SIZE);
+ }
+ }
+
+ return sManager.addDelegate(newDelegate);
+ }
+
+ public static boolean native_isIdentity(int native_object) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ for (int i = 0, k = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++, k++) {
+ if (d.mValues[k] != ((i==j) ? 1 : 0)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public static boolean native_rectStaysRect(int native_object) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return true;
+ }
+
+ return (d.computeTypeMask() & kRectStaysRect_Mask) != 0;
+ }
+
+ public static void native_reset(int native_object) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ reset(d.mValues);
+ }
+
+ public static void native_set(int native_object, int other) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ Matrix_Delegate src = sManager.getDelegate(other);
+ if (src == null) {
+ assert false;
+ return;
+ }
+
+ System.arraycopy(src.mValues, 0, d.mValues, 0, MATRIX_SIZE);
+ }
+
+ public static void native_setTranslate(int native_object, float dx, float dy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ setTranslate(d.mValues, dx, dy);
+ }
+
+ public static void native_setScale(int native_object, float sx, float sy, float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ d.mValues = getScale(sx, sy, px, py);
+ }
+
+ public static void native_setScale(int native_object, float sx, float sy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ d.mValues[0] = sx;
+ d.mValues[1] = 0;
+ d.mValues[2] = 0;
+ d.mValues[3] = 0;
+ d.mValues[4] = sy;
+ d.mValues[5] = 0;
+ d.mValues[6] = 0;
+ d.mValues[7] = 0;
+ d.mValues[8] = 1;
+ }
+
+ public static void native_setRotate(int native_object, float degrees, float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ d.mValues = getRotate(degrees, px, py);
+ }
+
+ public static void native_setRotate(int native_object, float degrees) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ setRotate(d.mValues, degrees);
+ }
+
+ public static void native_setSinCos(int native_object, float sinValue, float cosValue,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ // TODO: do it in one pass
+
+ // translate so that the pivot is in 0,0
+ setTranslate(d.mValues, -px, -py);
+
+ // scale
+ d.postTransform(getRotate(sinValue, cosValue));
+ // translate back the pivot
+ d.postTransform(getTranslate(px, py));
+ }
+
+ public static void native_setSinCos(int native_object, float sinValue, float cosValue) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ setRotate(d.mValues, sinValue, cosValue);
+ }
+
+ public static void native_setSkew(int native_object, float kx, float ky, float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ d.mValues = getSkew(kx, ky, px, py);
+ }
+
+ public static void native_setSkew(int native_object, float kx, float ky) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ d.mValues[0] = 1;
+ d.mValues[1] = kx;
+ d.mValues[2] = -0;
+ d.mValues[3] = ky;
+ d.mValues[4] = 1;
+ d.mValues[5] = 0;
+ d.mValues[6] = 0;
+ d.mValues[7] = 0;
+ d.mValues[8] = 1;
+ }
+
+ public static boolean native_setConcat(int native_object, int a, int b) {
+ if (a == native_object) {
+ return native_preConcat(native_object, b);
+ } else if (b == native_object) {
+ return native_postConcat(native_object, a);
+ }
+
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ Matrix_Delegate a_mtx = sManager.getDelegate(a);
+ if (a_mtx == null) {
+ assert false;
+ return false;
+ }
+
+ Matrix_Delegate b_mtx = sManager.getDelegate(b);
+ if (b_mtx == null) {
+ assert false;
+ return false;
+ }
+
+ multiply(d.mValues, a_mtx.mValues, b_mtx.mValues);
+
+ return true;
+ }
+
+ public static boolean native_preTranslate(int native_object, float dx, float dy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.preTransform(getTranslate(dx, dy));
+ return true;
+ }
+
+ public static boolean native_preScale(int native_object, float sx, float sy,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.preTransform(getScale(sx, sy, px, py));
+ return true;
+ }
+
+ public static boolean native_preScale(int native_object, float sx, float sy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.preTransform(getScale(sx, sy));
+ return true;
+ }
+
+ public static boolean native_preRotate(int native_object, float degrees, float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.preTransform(getRotate(degrees, px, py));
+ return true;
+ }
+
+ public static boolean native_preRotate(int native_object, float degrees) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ double rad = Math.toRadians(degrees);
+ float sin = (float)Math.sin(rad);
+ float cos = (float)Math.cos(rad);
+
+ d.preTransform(getRotate(sin, cos));
+ return true;
+ }
+
+ public static boolean native_preSkew(int native_object, float kx, float ky,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.preTransform(getSkew(kx, ky, px, py));
+ return true;
+ }
+
+ public static boolean native_preSkew(int native_object, float kx, float ky) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.preTransform(getSkew(kx, ky));
+ return true;
+ }
+
+ public static boolean native_preConcat(int native_object, int other_matrix) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ Matrix_Delegate other = sManager.getDelegate(other_matrix);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.preTransform(other.mValues);
+ return true;
+ }
+
+ public static boolean native_postTranslate(int native_object, float dx, float dy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.postTransform(getTranslate(dx, dy));
+ return true;
+ }
+
+ public static boolean native_postScale(int native_object, float sx, float sy,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.postTransform(getScale(sx, sy, px, py));
+ return true;
+ }
+
+ public static boolean native_postScale(int native_object, float sx, float sy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.postTransform(getScale(sx, sy));
+ return true;
+ }
+
+ public static boolean native_postRotate(int native_object, float degrees, float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.preTransform(getRotate(degrees, px, py));
+ return true;
+ }
+
+ public static boolean native_postRotate(int native_object, float degrees) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.postTransform(getRotate(degrees));
+ return true;
+ }
+
+ public static boolean native_postSkew(int native_object, float kx, float ky,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.postTransform(getSkew(kx, ky, px, py));
+ return true;
+ }
+
+ public static boolean native_postSkew(int native_object, float kx, float ky) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.postTransform(getSkew(kx, ky));
+ return true;
+ }
+
+ public static boolean native_postConcat(int native_object, int other_matrix) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ Matrix_Delegate other = sManager.getDelegate(other_matrix);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ d.postTransform(other.mValues);
+ return true;
+ }
+
+ public static boolean native_setRectToRect(int native_object, RectF src, RectF dst, int stf) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ if (src.isEmpty()) {
+ reset(d.mValues);
+ return false;
+ }
+
+ if (dst.isEmpty()) {
+ d.mValues[0] = d.mValues[1] = d.mValues[2] = d.mValues[3] = d.mValues[4] = d.mValues[5]
+ = d.mValues[6] = d.mValues[7] = 0;
+ d.mValues[8] = 1;
+ } else {
+ float tx, sx = dst.width() / src.width();
+ float ty, sy = dst.height() / src.height();
+ boolean xLarger = false;
+
+ if (stf != ScaleToFit.FILL.nativeInt) {
+ if (sx > sy) {
+ xLarger = true;
+ sx = sy;
+ } else {
+ sy = sx;
+ }
+ }
+
+ tx = dst.left - src.left * sx;
+ ty = dst.top - src.top * sy;
+ if (stf == ScaleToFit.CENTER.nativeInt || stf == ScaleToFit.END.nativeInt) {
+ float diff;
+
+ if (xLarger) {
+ diff = dst.width() - src.width() * sy;
+ } else {
+ diff = dst.height() - src.height() * sy;
+ }
+
+ if (stf == ScaleToFit.CENTER.nativeInt) {
+ diff = diff / 2;
+ }
+
+ if (xLarger) {
+ tx += diff;
+ } else {
+ ty += diff;
+ }
+ }
+
+ d.mValues[0] = sx;
+ d.mValues[4] = sy;
+ d.mValues[2] = tx;
+ d.mValues[5] = ty;
+ d.mValues[1] = d.mValues[3] = d.mValues[6] = d.mValues[7] = 0;
+
+ }
+ // shared cleanup
+ d.mValues[8] = 1;
+ return true;
+ }
+
+ public static boolean native_setPolyToPoly(int native_object, float[] src, int srcIndex,
+ float[] dst, int dstIndex, int pointCount) {
+ // FIXME
+ throw new UnsupportedOperationException("NATIVE DELEGATE NEEDED");
+ }
+
+ public static boolean native_invert(int native_object, int inverse) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ Matrix_Delegate inv_mtx = sManager.getDelegate(inverse);
+ if (inv_mtx == null) {
+ assert false;
+ return false;
+ }
+
+
+ try {
+ AffineTransform affineTransform = getAffineTransform(d);
+ AffineTransform inverseTransform = affineTransform.createInverse();
+ inv_mtx.mValues[0] = (float)inverseTransform.getScaleX();
+ inv_mtx.mValues[1] = (float)inverseTransform.getShearX();
+ inv_mtx.mValues[2] = (float)inverseTransform.getTranslateX();
+ inv_mtx.mValues[3] = (float)inverseTransform.getScaleX();
+ inv_mtx.mValues[4] = (float)inverseTransform.getShearY();
+ inv_mtx.mValues[5] = (float)inverseTransform.getTranslateY();
+
+ return true;
+ } catch (NoninvertibleTransformException e) {
+ return false;
+ }
+ }
+
+ public static void native_mapPoints(int native_object, float[] dst, int dstIndex,
+ float[] src, int srcIndex, int ptCount, boolean isPts) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ if (isPts) {
+ d.mapPoints(dst, dstIndex, src, srcIndex, ptCount);
+ } else {
+ // src is vectors
+ // FIXME
+ throw new UnsupportedOperationException("NATIVE DELEGATE NEEDED");
+ }
+ }
+
+ public static boolean native_mapRect(int native_object, RectF dst, RectF src) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return false;
+ }
+
+ // array with 4 corners
+ float[] corners = new float[] {
+ src.left, src.top,
+ src.right, src.top,
+ src.right, src.bottom,
+ src.left, src.bottom,
+ };
+
+ // apply the transform to them.
+ d.mapPoints(corners);
+
+ // now put the result in the rect. We take the min/max of Xs and min/max of Ys
+ dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
+ dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
+
+ dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
+ dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
+
+
+ return (d.computeTypeMask() & kRectStaysRect_Mask) != 0;
+ }
+
+ public static float native_mapRadius(int native_object, float radius) {
+ // FIXME
+ throw new UnsupportedOperationException();
+ }
+
+ public static void native_getValues(int native_object, float[] values) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ System.arraycopy(d.mValues, 0, d.mValues, 0, MATRIX_SIZE);
+ }
+
+ public static void native_setValues(int native_object, float[] values) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ assert false;
+ return;
+ }
+
+ System.arraycopy(values, 0, d.mValues, 0, MATRIX_SIZE);
+ }
+
+ public static boolean native_equals(int native_a, int native_b) {
+ Matrix_Delegate a = sManager.getDelegate(native_a);
+ if (a == null) {
+ assert false;
+ return false;
+ }
+
+ Matrix_Delegate b = sManager.getDelegate(native_b);
+ if (b == null) {
+ assert false;
+ return false;
+ }
+
+ for (int i = 0 ; i < MATRIX_SIZE ; i++) {
+ if (a.mValues[i] != b.mValues[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static void finalizer(int native_instance) {
+ sManager.removeDelegate(native_instance);
+ }
+
+ // ---- Private helper methods ----
+
+ private static AffineTransform getAffineTransform(Matrix_Delegate d) {
+ // the AffineTransform constructor takes the value in a different order
+ // for a matrix [ 0 1 2 ]
+ // [ 3 4 5 ]
+ // the order is 0, 3, 1, 4, 2, 5...
+ return new AffineTransform(
+ d.mValues[0], d.mValues[3], d.mValues[1],
+ d.mValues[4], d.mValues[2], d.mValues[5]);
+ }
+
+
+ /**
+ * Reset a matrix to the identity
+ */
+ private static void reset(float[] mtx) {
+ for (int i = 0, k = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++, k++) {
+ mtx[k] = ((i==j) ? 1 : 0);
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private final static int kIdentity_Mask = 0;
+ private final static int kTranslate_Mask = 0x01; //!< set if the matrix has translation
+ private final static int kScale_Mask = 0x02; //!< set if the matrix has X or Y scale
+ private final static int kAffine_Mask = 0x04; //!< set if the matrix skews or rotates
+ private final static int kPerspective_Mask = 0x08; //!< set if the matrix is in perspective
+ private final static int kRectStaysRect_Mask = 0x10;
+ @SuppressWarnings("unused")
+ private final static int kUnknown_Mask = 0x80;
+
+ @SuppressWarnings("unused")
+ private final static int kAllMasks = kTranslate_Mask |
+ kScale_Mask |
+ kAffine_Mask |
+ kPerspective_Mask |
+ kRectStaysRect_Mask;
+
+ // these guys align with the masks, so we can compute a mask from a variable 0/1
+ @SuppressWarnings("unused")
+ private final static int kTranslate_Shift = 0;
+ @SuppressWarnings("unused")
+ private final static int kScale_Shift = 1;
+ @SuppressWarnings("unused")
+ private final static int kAffine_Shift = 2;
+ @SuppressWarnings("unused")
+ private final static int kPerspective_Shift = 3;
+ private final static int kRectStaysRect_Shift = 4;
+
+ private int computeTypeMask() {
+ int mask = 0;
+
+ if (mValues[6] != 0. || mValues[7] != 0. || mValues[8] != 1.) {
+ mask |= kPerspective_Mask;
+ }
+
+ if (mValues[2] != 0. || mValues[5] != 0.) {
+ mask |= kTranslate_Mask;
+ }
+
+ float m00 = mValues[0];
+ float m01 = mValues[1];
+ float m10 = mValues[3];
+ float m11 = mValues[4];
+
+ if (m01 != 0. || m10 != 0.) {
+ mask |= kAffine_Mask;
+ }
+
+ if (m00 != 1. || m11 != 1.) {
+ mask |= kScale_Mask;
+ }
+
+ if ((mask & kPerspective_Mask) == 0) {
+ // map non-zero to 1
+ int im00 = m00 != 0 ? 1 : 0;
+ int im01 = m01 != 0 ? 1 : 0;
+ int im10 = m10 != 0 ? 1 : 0;
+ int im11 = m11 != 0 ? 1 : 0;
+
+ // record if the (p)rimary and (s)econdary diagonals are all 0 or
+ // all non-zero (answer is 0 or 1)
+ int dp0 = (im00 | im11) ^ 1; // true if both are 0
+ int dp1 = im00 & im11; // true if both are 1
+ int ds0 = (im01 | im10) ^ 1; // true if both are 0
+ int ds1 = im01 & im10; // true if both are 1
+
+ // return 1 if primary is 1 and secondary is 0 or
+ // primary is 0 and secondary is 1
+ mask |= ((dp0 & ds1) | (dp1 & ds0)) << kRectStaysRect_Shift;
+ }
+
+ return mask;
+ }
+
+ /**
+ * Adds the given transformation to the current Matrix
+ * <p/>This in effect does this = this*matrix
+ * @param matrix
+ */
+ private void postTransform(float[] matrix) {
+ float[] tmp = new float[9];
+ multiply(tmp, mValues, matrix);
+ mValues = tmp;
+ }
+
+ /**
+ * Adds the given transformation to the current Matrix
+ * <p/>This in effect does this = matrix*this
+ * @param matrix
+ */
+ private void preTransform(float[] matrix) {
+ float[] tmp = new float[9];
+ multiply(tmp, matrix, mValues);
+ mValues = tmp;
+ }
+
+ /**
+ * Apply this matrix to the array of 2D points specified by src, and write
+ * the transformed points into the array of points specified by dst. The
+ * two arrays represent their "points" as pairs of floats [x, y].
+ *
+ * @param dst The array of dst points (x,y pairs)
+ * @param dstIndex The index of the first [x,y] pair of dst floats
+ * @param src The array of src points (x,y pairs)
+ * @param srcIndex The index of the first [x,y] pair of src floats
+ * @param pointCount The number of points (x,y pairs) to transform
+ */
+
+ private void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,
+ int pointCount) {
+ //checkPointArrays(src, srcIndex, dst, dstIndex, pointCount);
+
+ float[] tmpDest = dst;
+ boolean inPlace = dst == src;
+ if (inPlace) {
+ tmpDest = new float[dstIndex + pointCount * 2];
+ }
+
+ for (int i = 0 ; i < pointCount ; i++) {
+ // just in case we are doing in place, we better put this in temp vars
+ float x = mValues[0] * src[i + srcIndex] +
+ mValues[1] * src[i + srcIndex + 1] +
+ mValues[2];
+ float y = mValues[3] * src[i + srcIndex] +
+ mValues[4] * src[i + srcIndex + 1] +
+ mValues[5];
+
+ tmpDest[i + dstIndex] = x;
+ tmpDest[i + dstIndex + 1] = y;
+ }
+
+ if (inPlace) {
+ System.arraycopy(tmpDest, dstIndex, dst, dstIndex, pointCount * 2);
+ }
+ }
+
+ /**
+ * Apply this matrix to the array of 2D points, and write the transformed
+ * points back into the array
+ *
+ * @param pts The array [x0, y0, x1, y1, ...] of points to transform.
+ */
+
+ private void mapPoints(float[] pts) {
+ mapPoints(pts, 0, pts, 0, pts.length >> 1);
+ }
+
+ /**
+ * multiply two matrices and store them in a 3rd.
+ * <p/>This in effect does dest = a*b
+ * dest cannot be the same as a or b.
+ */
+ private static void multiply(float dest[], float[] a, float[] b) {
+ // first row
+ dest[0] = b[0] * a[0] + b[1] * a[3] + b[2] * a[6];
+ dest[1] = b[0] * a[1] + b[1] * a[4] + b[2] * a[7];
+ dest[2] = b[0] * a[2] + b[1] * a[5] + b[2] * a[8];
+
+ // 2nd row
+ dest[3] = b[3] * a[0] + b[4] * a[3] + b[5] * a[6];
+ dest[4] = b[3] * a[1] + b[4] * a[4] + b[5] * a[7];
+ dest[5] = b[3] * a[2] + b[4] * a[5] + b[5] * a[8];
+
+ // 3rd row
+ dest[6] = b[6] * a[0] + b[7] * a[3] + b[8] * a[6];
+ dest[7] = b[6] * a[1] + b[7] * a[4] + b[8] * a[7];
+ dest[8] = b[6] * a[2] + b[7] * a[5] + b[8] * a[8];
+ }
+
+ /**
+ * Returns a matrix that represents a given translate
+ * @param dx
+ * @param dy
+ * @return
+ */
+ private static float[] getTranslate(float dx, float dy) {
+ return setTranslate(new float[9], dx, dy);
+ }
+
+ private static float[] setTranslate(float[] dest, float dx, float dy) {
+ dest[0] = 1;
+ dest[1] = 0;
+ dest[2] = dx;
+ dest[3] = 0;
+ dest[4] = 1;
+ dest[5] = dy;
+ dest[6] = 0;
+ dest[7] = 0;
+ dest[8] = 1;
+ return dest;
+ }
+
+ private static float[] getScale(float sx, float sy) {
+ return new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 };
+ }
+
+ /**
+ * Returns a matrix that represents the given scale info.
+ * @param sx
+ * @param sy
+ * @param px
+ * @param py
+ */
+ private static float[] getScale(float sx, float sy, float px, float py) {
+ float[] tmp = new float[9];
+ float[] tmp2 = new float[9];
+
+ // TODO: do it in one pass
+
+ // translate tmp so that the pivot is in 0,0
+ setTranslate(tmp, -px, -py);
+
+ // scale into tmp2
+ multiply(tmp2, tmp, getScale(sx, sy));
+
+ // translate back the pivot back into tmp
+ multiply(tmp, tmp2, getTranslate(px, py));
+
+ return tmp;
+ }
+
+
+ private static float[] getRotate(float degrees) {
+ double rad = Math.toRadians(degrees);
+ float sin = (float)Math.sin(rad);
+ float cos = (float)Math.cos(rad);
+
+ return getRotate(sin, cos);
+ }
+
+ private static float[] getRotate(float sin, float cos) {
+ return setRotate(new float[9], sin, cos);
+ }
+
+ private static float[] setRotate(float[] dest, float degrees) {
+ double rad = Math.toRadians(degrees);
+ float sin = (float)Math.sin(rad);
+ float cos = (float)Math.cos(rad);
+
+ return setRotate(dest, sin, cos);
+ }
+
+ private static float[] setRotate(float[] dest, float sin, float cos) {
+ dest[0] = cos;
+ dest[1] = -sin;
+ dest[2] = 0;
+ dest[3] = sin;
+ dest[4] = cos;
+ dest[5] = 0;
+ dest[6] = 0;
+ dest[7] = 0;
+ dest[8] = 1;
+ return dest;
+ }
+
+ private static float[] getRotate(float degrees, float px, float py) {
+ float[] tmp = new float[9];
+ float[] tmp2 = new float[9];
+
+ // TODO: do it in one pass
+
+ // translate so that the pivot is in 0,0
+ setTranslate(tmp, -px, -py);
+
+ // rotate into tmp2
+ double rad = Math.toRadians(degrees);
+ float cos = (float)Math.cos(rad);
+ float sin = (float)Math.sin(rad);
+ multiply(tmp2, tmp, getRotate(sin, cos));
+
+ // translate back the pivot back into tmp
+ multiply(tmp, tmp2, getTranslate(px, py));
+
+ return tmp;
+ }
+
+ private static float[] getSkew(float kx, float ky) {
+ return new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 };
+ }
+
+ private static float[] getSkew(float kx, float ky, float px, float py) {
+ float[] tmp = new float[9];
+ float[] tmp2 = new float[9];
+
+ // TODO: do it in one pass
+
+ // translate so that the pivot is in 0,0
+ setTranslate(tmp, -px, -py);
+
+ // skew into tmp2
+ multiply(tmp2, tmp, new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 });
+ // translate back the pivot back into tmp
+ multiply(tmp, tmp2, getTranslate(px, py));
+
+ return tmp;
+ }
+
+
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path.java b/tools/layoutlib/bridge/src/android/graphics/Path.java
index 12d2cde..c0bc005 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path.java
@@ -31,13 +31,13 @@ import java.awt.geom.Rectangle2D;
* text on a path.
*/
public class Path {
-
+
private FillType mFillType = FillType.WINDING;
private GeneralPath mPath = new GeneralPath();
-
+
private float mLastX = 0;
private float mLastY = 0;
-
+
//---------- Custom methods ----------
public Shape getAwtShape() {
@@ -60,7 +60,7 @@ public class Path {
public Path(Path src) {
mPath.append(src.mPath, false /* connect */);
}
-
+
/**
* Clear any lines and curves from the path, making it empty.
* This does NOT change the fill-type setting.
@@ -92,7 +92,7 @@ public class Path {
EVEN_ODD (GeneralPath.WIND_EVEN_ODD, false),
INVERSE_WINDING (GeneralPath.WIND_NON_ZERO, true),
INVERSE_EVEN_ODD(GeneralPath.WIND_EVEN_ODD, true);
-
+
FillType(int rule, boolean inverse) {
this.rule = rule;
this.inverse = inverse;
@@ -101,7 +101,7 @@ public class Path {
final int rule;
final boolean inverse;
}
-
+
/**
* Return the path's fill type. This defines how "inside" is
* computed. The default value is WINDING.
@@ -121,7 +121,7 @@ public class Path {
mFillType = ft;
mPath.setWindingRule(ft.rule);
}
-
+
/**
* Returns true if the filltype is one of the INVERSE variants
*
@@ -130,7 +130,7 @@ public class Path {
public boolean isInverseFillType() {
return mFillType.inverse;
}
-
+
/**
* Toggles the INVERSE state of the filltype
*/
@@ -150,7 +150,7 @@ public class Path {
break;
}
}
-
+
/**
* Returns true if the path is empty (contains no lines or curves)
*
@@ -350,7 +350,7 @@ public class Path {
boolean forceMoveTo) {
throw new UnsupportedOperationException();
}
-
+
/**
* Append the specified arc to the path as a new contour. If the start of
* the path is different from the path's current last point, then an
@@ -365,7 +365,7 @@ public class Path {
public void arcTo(RectF oval, float startAngle, float sweepAngle) {
throw new UnsupportedOperationException();
}
-
+
/**
* Close the current contour. If the current point is not equal to the
* first point of the contour, a line segment is automatically added.
@@ -383,13 +383,13 @@ public class Path {
CW (0), // must match enum in SkPath.h
/** counter-clockwise */
CCW (1); // must match enum in SkPath.h
-
+
Direction(int ni) {
nativeInt = ni;
}
final int nativeInt;
}
-
+
/**
* Add a closed rectangle contour to the path
*
@@ -400,7 +400,7 @@ public class Path {
if (rect == null) {
throw new NullPointerException("need rect parameter");
}
-
+
addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
}
@@ -446,7 +446,7 @@ public class Path {
// FIXME Need to support direction
Ellipse2D ovalShape = new Ellipse2D.Float(oval.left, oval.top, oval.width(), oval.height());
-
+
mPath.append(ovalShape, false /* connect */);
}
@@ -493,7 +493,7 @@ public class Path {
// FIXME
throw new UnsupportedOperationException();
}
-
+
/**
* Add a closed round-rectangle contour to the path. Each corner receives
* two radius values [X, Y]. The corners are ordered top-left, top-right,
@@ -513,7 +513,7 @@ public class Path {
// FIXME
throw new UnsupportedOperationException();
}
-
+
/**
* Add a copy of src to the path, offset by (dx,dy)
*
@@ -554,11 +554,11 @@ public class Path {
*/
public void offset(float dx, float dy, Path dst) {
GeneralPath newPath = new GeneralPath();
-
+
PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
-
+
newPath.append(iterator, false /* connect */);
-
+
if (dst != null) {
dst.mPath = newPath;
} else {
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 0910d79..4bc8855 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -116,10 +116,10 @@ public final class BridgeInflater extends LayoutInflater {
}
@Override
- public View createViewFromTag(String name, AttributeSet attrs) {
+ public View createViewFromTag(View parent, String name, AttributeSet attrs) {
View view = null;
try {
- view = super.createViewFromTag(name, attrs);
+ view = super.createViewFromTag(parent, name, attrs);
} catch (InflateException e) {
// try to load the class from using the custom view loader
try {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index f91f601..eb0eba2 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -30,6 +30,8 @@ import com.android.ninepatch.NinePatch;
import com.android.tools.layoutlib.create.MethodAdapter;
import com.android.tools.layoutlib.create.OverrideMethod;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -46,6 +48,7 @@ import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.BridgeInflater;
+import android.view.DragEvent;
import android.view.InputChannel;
import android.view.IWindow;
import android.view.IWindowSession;
@@ -1073,6 +1076,38 @@ public final class Bridge implements ILayoutBridge {
}
@SuppressWarnings("unused")
+ public IBinder prepareDrag(IWindow window, boolean localOnly,
+ int thumbnailWidth, int thumbnailHeight, Surface outSurface)
+ throws RemoteException {
+ // pass for now
+ return null;
+ }
+
+ @SuppressWarnings("unused")
+ public boolean performDrag(IWindow window, IBinder dragToken,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data)
+ throws RemoteException {
+ // pass for now
+ return false;
+ }
+
+ @SuppressWarnings("unused")
+ public void reportDropResult(IWindow window, boolean consumed) throws RemoteException {
+ // pass for now
+ }
+
+ @SuppressWarnings("unused")
+ public void dragRecipientEntered(IWindow window) throws RemoteException {
+ // pass for now
+ }
+
+ @SuppressWarnings("unused")
+ public void dragRecipientExited(IWindow window) throws RemoteException {
+ // pass for now
+ }
+
+ @SuppressWarnings("unused")
public void setWallpaperPosition(IBinder window, float x, float y,
float xStep, float yStep) {
// pass for now.
@@ -1170,6 +1205,11 @@ public final class Bridge implements ILayoutBridge {
// pass for now.
}
+ @SuppressWarnings("unused")
+ public void dispatchDragEvent(DragEvent event) {
+ // pass for now.
+ }
+
public IBinder asBinder() {
// pass for now.
return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java
index 20ccc0b..a063455 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java
@@ -117,6 +117,17 @@ public class BridgeContentResolver extends ContentResolver {
return null;
}
+ public String[] getStreamTypes(Uri arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public AssetFileDescriptor openTypedAssetFile(Uri arg0, String arg1, Bundle arg2)
+ throws RemoteException, FileNotFoundException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
}
public BridgeContentResolver(Context context) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
index ecd22e2..8592731 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
@@ -37,6 +37,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
+import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
@@ -1067,6 +1068,13 @@ public final class BridgeContext extends Context {
}
@Override
+ public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1,
+ CursorFactory arg2, DatabaseErrorHandler arg3) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
public Drawable peekWallpaper() {
// TODO Auto-generated method stub
return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/DelegateManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/DelegateManager.java
new file mode 100644
index 0000000..3d9f960
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/DelegateManager.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge;
+
+import android.util.SparseArray;
+
+/**
+ * Manages native delegates.
+ *
+ * This is used in conjunction with layoublib_create: certain Android java classes are mere
+ * wrappers around a heavily native based implementation, and we need a way to run these classes
+ * in our Eclipse rendering framework without bringing all the native code from the Android
+ * platform.
+ *
+ * Thus we instruct layoutlib_create to modify the bytecode of these classes to replace their
+ * native methods by "delegate calls".
+ *
+ * For example, a native method android.graphics.Matrix.init(...) will actually become
+ * a call to android.graphics.Matrix_Delegate.init(...).
+ *
+ * The Android java classes that use native code uses an int (Java side) to reference native
+ * objects. This int is generally directly the pointer to the C structure counterpart.
+ * Typically a creation method will return such an int, and then this int will be passed later
+ * to a Java method to identify the C object to manipulate.
+ *
+ * Since we cannot use the Java object reference as the int directly, DelegateManager manages the
+ * int -> Delegate class link.
+ *
+ * Native methods usually always have the int as parameters. The first thing the delegate method
+ * will do is call {@link #getDelegate(int)} to get the Java object matching the int.
+ *
+ * Typical native init methods are returning a new int back to the Java class, so
+ * {@link #addDelegate(Object)} does the same.
+ *
+ * @param <T> the delegate class to manage
+ */
+public final class DelegateManager<T> {
+
+ private final SparseArray<T> mDelegates = new SparseArray<T>();
+ private int mDelegateCounter = 0;
+
+ /**
+ * Returns the delegate from the given native int.
+ * @param native_object the native int.
+ * @return the delegate or null if not found.
+ */
+ public T getDelegate(int native_object) {
+ synchronized (mDelegates) {
+ return mDelegates.get(native_object);
+ }
+ }
+
+ /**
+ * Adds a delegate to the manager and returns the native int used to identify it.
+ * @param newDelegate the delegate to add
+ * @return a unique native int to identify the delegate
+ */
+ public int addDelegate(T newDelegate) {
+ synchronized (mDelegates) {
+ int native_object = ++mDelegateCounter;
+ mDelegates.put(native_object, newDelegate);
+ return native_object;
+ }
+ }
+
+ /**
+ * Removes the delegate matching the given native int.
+ * @param native_object the native int.
+ */
+ public void removeDelegate(int native_object) {
+ synchronized (mDelegates) {
+ mDelegates.remove(native_object);
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java
index e0dc55f..adb693d 100644
--- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java
+++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java
@@ -17,6 +17,8 @@
package com.android.layoutlib.bridge;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
import junit.framework.TestCase;
@@ -26,7 +28,8 @@ public class TestClassReplacement extends TestCase {
// TODO: we want to test all the classes. For now only Paint passes the tests.
// final String[] classes = CreateInfo.RENAMED_CLASSES;
final String[] classes = new String[] {
- "android.graphics.Paint", "android.graphics._Original_Paint"
+ "android.graphics.Paint", "android.graphics._Original_Paint",
+ "android.graphics.Canvas", "android.graphics._Original_Canvas",
};
final int count = classes.length;
for (int i = 0 ; i < count ; i += 2) {
@@ -52,12 +55,21 @@ public class TestClassReplacement extends TestCase {
Method[] oldClassMethods = oldClass.getDeclaredMethods();
for (Method oldMethod : oldClassMethods) {
- // we ignore anything that starts with native
+ // we ignore anything that starts with native. This is because the class we are looking
+ // at has already been modified to remove the native modifiers.
if (oldMethod.getName().startsWith("native")) {
continue;
}
+
+ // or static and private
+ int privateStatic = Modifier.STATIC | Modifier.PRIVATE;
+ if ((oldMethod.getModifiers() & privateStatic) == privateStatic) {
+ continue;
+ }
+
boolean found = false;
for (Method newMethod : newClassMethods) {
+
if (compareMethods(newClass, newMethod, oldClass, oldMethod)) {
found = true;
break;
@@ -65,7 +77,31 @@ public class TestClassReplacement extends TestCase {
}
if (found == false) {
- fail(String.format("Unable to find %1$s", oldMethod.toGenericString()));
+ // compute a full class name that's long but not too long.
+ StringBuilder sb = new StringBuilder(oldMethod.getName() + "(");
+ Type[] params = oldMethod.getGenericParameterTypes();
+ for (int j = 0; j < params.length; j++) {
+ if (params[j] instanceof Class) {
+ Class theClass = (Class)params[j];
+ sb.append(theClass.getName());
+ int dimensions = 0;
+ while (theClass.isArray()) {
+ dimensions++;
+ theClass = theClass.getComponentType();
+ }
+ for (int i = 0; i < dimensions; i++) {
+ sb.append("[]");
+ }
+
+ } else {
+ sb.append(params[j].toString());
+ }
+ if (j < (params.length - 1))
+ sb.append(",");
+ }
+ sb.append(")");
+
+ fail(String.format("Missing %1$s.%2$s", newClass.getName(), sb.toString()));
}
}
diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestNativeDelegate.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestNativeDelegate.java
new file mode 100644
index 0000000..6eed8ba
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestNativeDelegate.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.create.CreateInfo;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests that native delegate classes implement all the required methods.
+ *
+ * This looks at {@link CreateInfo#DELEGATE_CLASS_NATIVES} to get the list of classes that
+ * have their native methods reimplemented through a delegate.
+ *
+ * Since the reimplemented methods are not native anymore, we look for the annotation
+ * {@link LayoutlibDelegate}, and look for a matching method in the delegate (named the same
+ * as the modified class with _Delegate added as a suffix).
+ * If the original native method is not static, then we make sure the delegate method also
+ * include the original class as first parameter (to access "this").
+ *
+ */
+public class TestNativeDelegate extends TestCase {
+
+ public void testNativeDelegates() {
+
+ final String[] classes = CreateInfo.DELEGATE_CLASS_NATIVES;
+ final int count = classes.length;
+ for (int i = 0 ; i < count ; i++) {
+ loadAndCompareClasses(classes[i], classes[i] + "_Delegate");
+ }
+ }
+
+ private void loadAndCompareClasses(String originalClassName, String delegateClassName) {
+ // load the classes
+ try {
+ ClassLoader classLoader = TestNativeDelegate.class.getClassLoader();
+ Class<?> originalClass = classLoader.loadClass(originalClassName);
+ Class<?> delegateClass = classLoader.loadClass(delegateClassName);
+
+ compare(originalClass, delegateClass);
+ } catch (ClassNotFoundException e) {
+ fail("Failed to load class: " + e.getMessage());
+ } catch (SecurityException e) {
+ fail("Failed to load class: " + e.getMessage());
+ }
+ }
+
+ private void compare(Class<?> originalClass, Class<?> delegateClass) throws SecurityException {
+ Method[] originalMethods = originalClass.getDeclaredMethods();
+
+ for (Method originalMethod : originalMethods) {
+ // look for methods that were native: they have the LayoutlibDelegate annotation
+ if (originalMethod.getAnnotation(LayoutlibDelegate.class) == null) {
+ continue;
+ }
+
+ // get the signature.
+ Class<?>[] parameters = originalMethod.getParameterTypes();
+
+ // if the method is not static, then the class is added as the first parameter
+ // (for "this")
+ if ((originalMethod.getModifiers() & Modifier.STATIC) == 0) {
+
+ Class<?>[] newParameters = new Class<?>[parameters.length + 1];
+ newParameters[0] = originalClass;
+ System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
+ parameters = newParameters;
+ }
+
+ try {
+ // try to load the method with the given parameter types.
+ delegateClass.getMethod(originalMethod.getName(), parameters);
+ } catch (NoSuchMethodException e) {
+ // compute a full class name that's long but not too long.
+ StringBuilder sb = new StringBuilder(originalMethod.getName() + "(");
+ for (int j = 0; j < parameters.length; j++) {
+ Class<?> theClass = parameters[j];
+ sb.append(theClass.getName());
+ int dimensions = 0;
+ while (theClass.isArray()) {
+ dimensions++;
+ theClass = theClass.getComponentType();
+ }
+ for (int i = 0; i < dimensions; i++) {
+ sb.append("[]");
+ }
+ if (j < (parameters.length - 1)) {
+ sb.append(",");
+ }
+ }
+ sb.append(")");
+
+ fail(String.format("Missing %1$s.%2$s", delegateClass.getName(), sb.toString()));
+ }
+ }
+ }
+}
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index c59e20d..65a64cd 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -195,5 +195,22 @@ example, the inner class Paint$Style in the Paint class should be discarded and
bridge will provide its own implementation.
+- References -
+--------------
+
+
+The JVM Specification 2nd edition:
+ http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
+
+Understanding bytecode:
+ http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
+
+Bytecode opcode list:
+ http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
+
+ASM user guide:
+ http://download.forge.objectweb.org/asm/asm-guide.pdf
+
+
--
end
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
new file mode 100644
index 0000000..9a48ea6
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a method that has been converted to a delegate by layoutlib_create.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LayoutlibDelegate {
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 7b55ed3e..a9ede26 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -28,9 +28,9 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
-import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
@@ -60,38 +60,56 @@ public class AsmGenerator {
* old-FQCN to rename and they get erased as they get renamed. At the end, classes still
* left here are not in the code base anymore and thus were not renamed. */
private HashSet<String> mClassesNotRenamed;
- /** A map { FQCN => map { list of return types to delete from the FQCN } }. */
+ /** A map { FQCN => set { list of return types to delete from the FQCN } }. */
private HashMap<String, Set<String>> mDeleteReturns;
+ /** A map { FQCN => set { method names } } of methods to rewrite as delegates.
+ * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
+ private final HashMap<String, Set<String>> mDelegateMethods;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
- *
+ *
* @param log Output logger.
* @param osDestJar The path of the destination JAR to create.
- * @param injectClasses The list of class from layoutlib_create to inject in layoutlib.
- * @param stubMethods The list of methods to stub out. Each entry must be in the form
- * "package.package.OuterClass$InnerClass#MethodName".
- * @param renameClasses The list of classes to rename, must be an even list: the binary FQCN
- * of class to replace followed by the new FQCN.
- * @param deleteReturns List of classes for which the methods returning them should be deleted.
- * The array contains a list of null terminated section starting with the name of the class
- * to rename in which the methods are deleted, followed by a list of return types identifying
- * the methods to delete.
+ * @param createInfo Creation parameters. Must not be null.
*/
- public AsmGenerator(Log log, String osDestJar,
- Class<?>[] injectClasses,
- String[] stubMethods,
- String[] renameClasses, String[] deleteReturns) {
+ public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
mLog = log;
mOsDestJar = osDestJar;
- mInjectClasses = injectClasses != null ? injectClasses : new Class<?>[0];
- mStubMethods = stubMethods != null ? new HashSet<String>(Arrays.asList(stubMethods)) :
- new HashSet<String>();
+ mInjectClasses = createInfo.getInjectedClasses();
+ mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
+
+ // Create the map/set of methods to change to delegates
+ mDelegateMethods = new HashMap<String, Set<String>>();
+ for (String signature : createInfo.getDelegateMethods()) {
+ int pos = signature.indexOf('#');
+ if (pos <= 0 || pos >= signature.length() - 1) {
+ continue;
+ }
+ String className = binaryToInternalClassName(signature.substring(0, pos));
+ String methodName = signature.substring(pos + 1);
+ Set<String> methods = mDelegateMethods.get(className);
+ if (methods == null) {
+ methods = new HashSet<String>();
+ mDelegateMethods.put(className, methods);
+ }
+ methods.add(methodName);
+ }
+ for (String className : createInfo.getDelegateClassNatives()) {
+ className = binaryToInternalClassName(className);
+ Set<String> methods = mDelegateMethods.get(className);
+ if (methods == null) {
+ methods = new HashSet<String>();
+ mDelegateMethods.put(className, methods);
+ }
+ methods.add(DelegateClassAdapter.ALL_NATIVES);
+ }
// Create the map of classes to rename.
mRenameClasses = new HashMap<String, String>();
mClassesNotRenamed = new HashSet<String>();
- int n = renameClasses == null ? 0 : renameClasses.length;
+ String[] renameClasses = createInfo.getRenamedClasses();
+ int n = renameClasses.length;
for (int i = 0; i < n; i += 2) {
assert i + 1 < n;
// The ASM class names uses "/" separators, whereas regular FQCN use "."
@@ -100,38 +118,37 @@ public class AsmGenerator {
mRenameClasses.put(oldFqcn, newFqcn);
mClassesNotRenamed.add(oldFqcn);
}
-
+
// create the map of renamed class -> return type of method to delete.
mDeleteReturns = new HashMap<String, Set<String>>();
- if (deleteReturns != null) {
- Set<String> returnTypes = null;
- String renamedClass = null;
- for (String className : deleteReturns) {
- // if we reach the end of a section, add it to the main map
- if (className == null) {
- if (returnTypes != null) {
- mDeleteReturns.put(renamedClass, returnTypes);
- }
-
- renamedClass = null;
- continue;
- }
-
- // if the renamed class is null, this is the beginning of a section
- if (renamedClass == null) {
- renamedClass = binaryToInternalClassName(className);
- continue;
- }
-
- // just a standard return type, we add it to the list.
- if (returnTypes == null) {
- returnTypes = new HashSet<String>();
+ String[] deleteReturns = createInfo.getDeleteReturns();
+ Set<String> returnTypes = null;
+ String renamedClass = null;
+ for (String className : deleteReturns) {
+ // if we reach the end of a section, add it to the main map
+ if (className == null) {
+ if (returnTypes != null) {
+ mDeleteReturns.put(renamedClass, returnTypes);
}
- returnTypes.add(binaryToInternalClassName(className));
+
+ renamedClass = null;
+ continue;
}
+
+ // if the renamed class is null, this is the beginning of a section
+ if (renamedClass == null) {
+ renamedClass = binaryToInternalClassName(className);
+ continue;
+ }
+
+ // just a standard return type, we add it to the list.
+ if (returnTypes == null) {
+ returnTypes = new HashSet<String>();
+ }
+ returnTypes.add(binaryToInternalClassName(className));
}
}
-
+
/**
* Returns the list of classes that have not been renamed yet.
* <p/>
@@ -163,12 +180,12 @@ public class AsmGenerator {
public void setDeps(Map<String, ClassReader> deps) {
mDeps = deps;
}
-
+
/** Gets the map of classes to output as-is, except if they have native methods */
public Map<String, ClassReader> getKeep() {
return mKeep;
}
-
+
/** Gets the map of dependencies that must be completely stubbed */
public Map<String, ClassReader> getDeps() {
return mDeps;
@@ -177,7 +194,7 @@ public class AsmGenerator {
/** Generates the final JAR */
public void generate() throws FileNotFoundException, IOException {
TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
-
+
for (Class<?> clazz : mInjectClasses) {
String name = classToEntryPath(clazz);
InputStream is = ClassLoader.getSystemResourceAsStream(name);
@@ -186,7 +203,7 @@ public class AsmGenerator {
name = classNameToEntryPath(transformName(cr.getClassName()));
all.put(name, b);
}
-
+
for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
ClassReader cr = entry.getValue();
byte[] b = transform(cr, true /* stubNativesOnly */);
@@ -211,8 +228,8 @@ public class AsmGenerator {
/**
* Writes the JAR file.
- *
- * @param outStream The file output stream were to write the JAR.
+ *
+ * @param outStream The file output stream were to write the JAR.
* @param all The map of all classes to output.
* @throws IOException if an I/O error has occurred
*/
@@ -236,7 +253,7 @@ public class AsmGenerator {
String classNameToEntryPath(String className) {
return className.replaceAll("\\.", "/").concat(".class");
}
-
+
/**
* Utility method to get the JAR entry path from a Class name.
* e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
@@ -248,30 +265,32 @@ public class AsmGenerator {
name = "$" + clazz.getSimpleName() + name;
clazz = parent;
}
- return classNameToEntryPath(clazz.getCanonicalName() + name);
+ return classNameToEntryPath(clazz.getCanonicalName() + name);
}
/**
* Transforms a class.
* <p/>
* There are 3 kind of transformations:
- *
+ *
* 1- For "mock" dependencies classes, we want to remove all code from methods and replace
* by a stub. Native methods must be implemented with this stub too. Abstract methods are
* left intact. Modified classes must be overridable (non-private, non-final).
* Native methods must be made non-final, non-private.
- *
+ *
* 2- For "keep" classes, we want to rewrite all native methods as indicated above.
* If a class has native methods, it must also be made non-private, non-final.
- *
+ *
* Note that unfortunately static methods cannot be changed to non-static (since static and
* non-static are invoked differently.)
*/
byte[] transform(ClassReader cr, boolean stubNativesOnly) {
boolean hasNativeMethods = hasNativeMethods(cr);
+
+ // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass)
String className = cr.getClassName();
-
+
String newName = transformName(className);
// transformName returns its input argument if there's no need to rename the class
if (newName != className) {
@@ -288,16 +307,28 @@ public class AsmGenerator {
// Rewrite the new class from scratch, without reusing the constant pool from the
// original class reader.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
-
+
ClassVisitor rv = cw;
if (newName != className) {
rv = new RenameClassAdapter(cw, className, newName);
}
-
- TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
+
+ ClassVisitor cv = new TransformClassAdapter(mLog, mStubMethods,
mDeleteReturns.get(className),
newName, rv,
stubNativesOnly, stubNativesOnly || hasNativeMethods);
+
+ Set<String> delegateMethods = mDelegateMethods.get(className);
+ if (delegateMethods != null && !delegateMethods.isEmpty()) {
+ // If delegateMethods only contains one entry ALL_NATIVES and the class is
+ // known to have no native methods, just skip this step.
+ if (hasNativeMethods ||
+ !(delegateMethods.size() == 1 &&
+ delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) {
+ cv = new DelegateClassAdapter(mLog, cv, className, delegateMethods);
+ }
+ }
+
cr.accept(cv, 0 /* flags */);
return cw.toByteArray();
}
@@ -323,7 +354,7 @@ public class AsmGenerator {
return newName + className.substring(pos);
}
}
-
+
return className;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 2ed8641..2d0ee6d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -16,31 +16,111 @@
package com.android.tools.layoutlib.create;
-public class CreateInfo {
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Describes the work to be done by {@link AsmGenerator}.
+ */
+public final class CreateInfo implements ICreateInfo {
+
+ /**
+ * Returns the list of class from layoutlib_create to inject in layoutlib.
+ * The list can be empty but must not be null.
+ */
+ public Class<?>[] getInjectedClasses() {
+ return INJECTED_CLASSES;
+ }
+
+ /**
+ * Returns the list of methods to rewrite as delegates.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDelegateMethods() {
+ return DELEGATE_METHODS;
+ }
+
+ /**
+ * Returns the list of classes on which to delegate all native methods.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDelegateClassNatives() {
+ return DELEGATE_CLASS_NATIVES;
+ }
+
+ /**
+ * Returns The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * The list can be empty but must not be null.
+ */
+ public String[] getOverriddenMethods() {
+ return OVERRIDDEN_METHODS;
+ }
+
+ /**
+ * Returns the list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * The list can be empty but must not be null.
+ */
+ public String[] getRenamedClasses() {
+ return RENAMED_CLASSES;
+ }
+
+ /**
+ * Returns the list of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDeleteReturns() {
+ return DELETE_RETURNS;
+ }
+
+ //-----
+
/**
* The list of class from layoutlib_create to inject in layoutlib.
*/
- public final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
+ private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
OverrideMethod.class,
MethodListener.class,
MethodAdapter.class,
- CreateInfo.class
+ ICreateInfo.class,
+ CreateInfo.class,
+ LayoutlibDelegate.class
};
/**
+ * The list of methods to rewrite as delegates.
+ */
+ private final static String[] DELEGATE_METHODS = new String[] {
+ // TODO: comment out once DelegateClass is working
+ // "android.view.View#isInEditMode",
+ // "android.content.res.Resources$Theme#obtainStyledAttributes",
+ };
+
+ /**
+ * The list of classes on which to delegate all native methods.
+ */
+ private final static String[] DELEGATE_CLASS_NATIVES = new String[] {
+ "android.graphics.Matrix",
+ };
+
+ /**
* The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
*/
- public final static String[] OVERRIDDEN_METHODS = new String[] {
- "android.view.View#isInEditMode",
- "android.content.res.Resources$Theme#obtainStyledAttributes",
- };
+ private final static String[] OVERRIDDEN_METHODS = new String[] {
+ // TODO: remove once DelegateClass is working
+ "android.view.View#isInEditMode",
+ "android.content.res.Resources$Theme#obtainStyledAttributes",
+ };
/**
* The list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
*/
- public final static String[] RENAMED_CLASSES =
+ private final static String[] RENAMED_CLASSES =
new String[] {
"android.graphics.Bitmap", "android.graphics._Original_Bitmap",
"android.graphics.BitmapFactory", "android.graphics._Original_BitmapFactory",
@@ -49,7 +129,6 @@ public class CreateInfo {
"android.graphics.ComposeShader", "android.graphics._Original_ComposeShader",
"android.graphics.DashPathEffect", "android.graphics._Original_DashPathEffect",
"android.graphics.LinearGradient", "android.graphics._Original_LinearGradient",
- "android.graphics.Matrix", "android.graphics._Original_Matrix",
"android.graphics.Paint", "android.graphics._Original_Paint",
"android.graphics.Path", "android.graphics._Original_Path",
"android.graphics.PorterDuffXfermode", "android.graphics._Original_PorterDuffXfermode",
@@ -69,7 +148,7 @@ public class CreateInfo {
* to rename in which the methods are deleted, followed by a list of return types identifying
* the methods to delete.
*/
- public final static String[] REMOVED_METHODS =
+ private final static String[] DELETE_RETURNS =
new String[] {
"android.graphics.Paint", // class to delete methods from
"android.graphics.Paint$Align", // list of type identifying methods to delete
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
new file mode 100644
index 0000000..9cba8a0
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Set;
+
+/**
+ * A {@link DelegateClassAdapter} can transform some methods from a class into
+ * delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ */
+public class DelegateClassAdapter extends ClassAdapter {
+
+ public final static String ALL_NATIVES = "<<all_natives>>";
+
+ private final String mClassName;
+ private final Set<String> mDelegateMethods;
+ private final Log mLog;
+
+ /**
+ * Creates a new {@link DelegateClassAdapter} that can transform some methods
+ * from a class into delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ *
+ * @param log The logger object. Must not be null.
+ * @param cv the class visitor to which this adapter must delegate calls.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param delegateMethods The set of method names to modify and/or the
+ * special constant {@link #ALL_NATIVES} to convert all native methods.
+ */
+ public DelegateClassAdapter(Log log,
+ ClassVisitor cv,
+ String className,
+ Set<String> delegateMethods) {
+ super(cv);
+ mLog = log;
+ mClassName = className;
+ mDelegateMethods = delegateMethods;
+ }
+
+ //----------------------------------
+ // Methods from the ClassAdapter
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+
+ boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
+ mDelegateMethods.contains(name);
+
+ if (useDelegate) {
+ // remove native
+ access = access & ~Opcodes.ACC_NATIVE;
+ }
+
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+ if (useDelegate) {
+ DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName,
+ name, desc, isStatic);
+ if (isNative) {
+ // A native has no code to visit, so we need to generate it directly.
+ a.generateCode();
+ } else {
+ return a;
+ }
+ }
+ return mw;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
new file mode 100644
index 0000000..21d6682
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * This method adapter rewrites a method by discarding the original code and generating
+ * a call to a delegate. Original annotations are passed along unchanged.
+ * <p/>
+ * Calls are delegated to a class named <code>&lt;className&gt;_Delegate</code> with
+ * static methods matching the methods to be overridden here. The methods have the
+ * same return type. The argument type list is the same except the "this" reference is
+ * passed first for non-static methods.
+ * <p/>
+ * A new annotation is added.
+ * <p/>
+ * Note that native methods have, by definition, no code so there's nothing a visitor
+ * can visit. That means the caller must call {@link #generateCode()} directly for
+ * a native and use the visitor pattern for non-natives.
+ * <p/>
+ * Instances of this class are not re-usable. You need a new instance for each method.
+ */
+class DelegateMethodAdapter implements MethodVisitor {
+
+ /**
+ * Suffix added to delegate classes.
+ */
+ public static final String DELEGATE_SUFFIX = "_Delegate";
+
+ private static String CONSTRUCTOR = "<init>";
+ private static String CLASS_INIT = "<clinit>";
+
+ /** The parent method writer */
+ private MethodVisitor mParentVisitor;
+ /** Flag to output the first line number. */
+ private boolean mOutputFirstLineNumber = true;
+ /** The original method descriptor (return type + argument types.) */
+ private String mDesc;
+ /** True if the original method is static. */
+ private final boolean mIsStatic;
+ /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
+ private final String mClassName;
+ /** The method name. */
+ private final String mMethodName;
+ /** Logger object. */
+ private final Log mLog;
+ /** True if {@link #visitCode()} has been invoked. */
+ private boolean mVisitCodeCalled;
+
+ /**
+ * Creates a new {@link DelegateMethodAdapter} that will transform this method
+ * into a delegate call.
+ * <p/>
+ * See {@link DelegateMethodAdapter} for more details.
+ *
+ * @param log The logger object. Must not be null.
+ * @param mv the method visitor to which this adapter must delegate calls.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param methodName The simple name of the method.
+ * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
+ * {@link Type#getArgumentTypes(String)})
+ * @param isStatic True if the method is declared static.
+ */
+ public DelegateMethodAdapter(Log log,
+ MethodVisitor mv,
+ String className,
+ String methodName,
+ String desc,
+ boolean isStatic) {
+ mLog = log;
+ mParentVisitor = mv;
+ mClassName = className;
+ mMethodName = methodName;
+ mDesc = desc;
+ mIsStatic = isStatic;
+
+ if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
+ // We're going to simplify by not supporting constructors.
+ // The only trick with a constructor is to find the proper super constructor
+ // and call it (and deciding if we should mirror the original method call to
+ // a custom constructor or call a default one.)
+ throw new UnsupportedOperationException(
+ String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",
+ className, methodName, desc));
+ }
+ }
+
+ /**
+ * Generates the new code for the method.
+ * <p/>
+ * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
+ * (since they have no code to visit).
+ * <p/>
+ * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
+ * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
+ * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
+ * this method will be invoked from {@link MethodVisitor#visitEnd()}.
+ */
+ public void generateCode() {
+ /*
+ * The goal is to generate a call to a static delegate method.
+ * If this method is not-static, the first parameter will be this.
+ * All the parameters must be passed and then the eventual return type returned.
+ *
+ * Example, let's say we have a method such as
+ * public void method_1(int a, Object b, ArrayList<String> c) { ... }
+ *
+ * We'll want to create a body that calls a delegate method like this:
+ * TheClass_Delegate.method_1(this, a, b, c);
+ *
+ * The generated class name is the current class name with "_Delegate" appended to it.
+ * One thing to realize is that we don't care about generics -- since generic types
+ * are erased at runtime, they have no influence on the method being called.
+ */
+
+ // Add our annotation
+ AnnotationVisitor aw = mParentVisitor.visitAnnotation(
+ Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
+ true); // visible at runtime
+ aw.visitEnd();
+
+ if (!mVisitCodeCalled) {
+ // If this is a direct call to generateCode() as done by DelegateClassAdapter
+ // for natives, visitCode() hasn't been called yet.
+ mParentVisitor.visitCode();
+ mVisitCodeCalled = true;
+ }
+
+ int numVars = 0;
+
+ // Push "this" for an instance method, which is always ALOAD 0
+ if (!mIsStatic) {
+ mParentVisitor.visitVarInsn(Opcodes.ALOAD, numVars++);
+ }
+
+ // Push all other arguments
+ Type[] argTypes = Type.getArgumentTypes(mDesc);
+ for (Type t : argTypes) {
+ int size = t.getSize();
+ mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), numVars);
+ numVars += size;
+ }
+
+ // Construct the descriptor of the delegate. For a static method, it's the same
+ // however for an instance method we need to pass the 'this' reference first
+ String desc = mDesc;
+ if (!mIsStatic && argTypes.length > 0) {
+ Type[] argTypes2 = new Type[argTypes.length + 1];
+
+ argTypes2[0] = Type.getObjectType(mClassName);
+ System.arraycopy(argTypes, 0, argTypes2, 1, argTypes.length);
+
+ desc = Type.getMethodDescriptor(Type.getReturnType(mDesc), argTypes2);
+ }
+
+ String delegateClassName = mClassName + DELEGATE_SUFFIX;
+
+ // Invoke the static delegate
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ delegateClassName,
+ mMethodName,
+ desc);
+
+ Type returnType = Type.getReturnType(mDesc);
+ mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+
+ mParentVisitor.visitMaxs(numVars, numVars);
+ mParentVisitor.visitEnd();
+
+ // For debugging now. Maybe we should collect these and store them in
+ // a text file for helping create the delegates. We could also compare
+ // the text file to a golden and break the build on unsupported changes
+ // or regressions. Even better we could fancy-print something that looks
+ // like the expected Java method declaration.
+ mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
+ }
+
+ /* Pass down to visitor writer. In this implementation, either do nothing. */
+ public void visitCode() {
+ mVisitCodeCalled = true;
+ mParentVisitor.visitCode();
+ }
+
+ /*
+ * visitMaxs is called just before visitEnd if there was any code to rewrite.
+ * Skip the original.
+ */
+ public void visitMaxs(int maxStack, int maxLocals) {
+ }
+
+ /**
+ * End of visiting. Generate the messaging code.
+ */
+ public void visitEnd() {
+ generateCode();
+ }
+
+ /* Writes all annotation from the original method. */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return mParentVisitor.visitAnnotation(desc, visible);
+ }
+
+ /* Writes all annotation default values from the original method. */
+ public AnnotationVisitor visitAnnotationDefault() {
+ return mParentVisitor.visitAnnotationDefault();
+ }
+
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ /* Writes all attributes from the original method. */
+ public void visitAttribute(Attribute attr) {
+ mParentVisitor.visitAttribute(attr);
+ }
+
+ /*
+ * Only writes the first line number present in the original code so that source
+ * viewers can direct to the correct method, even if the content doesn't match.
+ */
+ public void visitLineNumber(int line, Label start) {
+ if (mOutputFirstLineNumber) {
+ mParentVisitor.visitLineNumber(line, start);
+ mOutputFirstLineNumber = false;
+ }
+ }
+
+ public void visitInsn(int opcode) {
+ // Skip original code.
+ }
+
+ public void visitLabel(Label label) {
+ // Skip original code.
+ }
+
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ // Skip original code.
+ }
+
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ // Skip original code.
+ }
+
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ // Skip original code.
+ }
+
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ // Skip original code.
+ }
+
+ public void visitIincInsn(int var, int increment) {
+ // Skip original code.
+ }
+
+ public void visitIntInsn(int opcode, int operand) {
+ // Skip original code.
+ }
+
+ public void visitJumpInsn(int opcode, Label label) {
+ // Skip original code.
+ }
+
+ public void visitLdcInsn(Object cst) {
+ // Skip original code.
+ }
+
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ // Skip original code.
+ }
+
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ // Skip original code.
+ }
+
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ // Skip original code.
+ }
+
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ // Skip original code.
+ }
+
+ public void visitTypeInsn(int opcode, String type) {
+ // Skip original code.
+ }
+
+ public void visitVarInsn(int opcode, int var) {
+ // Skip original code.
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
new file mode 100644
index 0000000..40c1706
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+/**
+ * Interface describing the work to be done by {@link AsmGenerator}.
+ */
+public interface ICreateInfo {
+
+ /**
+ * Returns the list of class from layoutlib_create to inject in layoutlib.
+ * The list can be empty but must not be null.
+ */
+ public abstract Class<?>[] getInjectedClasses();
+
+ /**
+ * Returns the list of methods to rewrite as delegates.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDelegateMethods();
+
+ /**
+ * Returns the list of classes on which to delegate all native methods.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDelegateClassNatives();
+
+ /**
+ * Returns The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getOverriddenMethods();
+
+ /**
+ * Returns the list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getRenamedClasses();
+
+ /**
+ * Returns the list of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDeleteReturns();
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index b30e9e5..43f2971 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -21,7 +21,28 @@ import java.util.ArrayList;
import java.util.Set;
-
+/**
+ * Entry point for the layoutlib_create tool.
+ * <p/>
+ * The tool does not currently rely on any external configuration file.
+ * Instead the configuration is mostly done via the {@link CreateInfo} class.
+ * <p/>
+ * For a complete description of the tool and its implementation, please refer to
+ * the "README.txt" file at the root of this project.
+ * <p/>
+ * For a quick test, invoke this as follows:
+ * <pre>
+ * $ make layoutlib
+ * </pre>
+ * which does:
+ * <pre>
+ * $ make layoutlib_create &lt;bunch of framework jars&gt;
+ * $ out/host/linux-x86/framework/bin/layoutlib_create \
+ * out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \
+ * out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \
+ * out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
+ * </pre>
+ */
public class Main {
public static void main(String[] args) {
@@ -42,12 +63,7 @@ public class Main {
}
try {
- AsmGenerator agen = new AsmGenerator(log, osDestJar[0],
- CreateInfo.INJECTED_CLASSES,
- CreateInfo.OVERRIDDEN_METHODS,
- CreateInfo.RENAMED_CLASSES,
- CreateInfo.REMOVED_METHODS
- );
+ AsmGenerator agen = new AsmGenerator(log, osDestJar[0], new CreateInfo());
AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
new String[] { "android.view.View" }, // derived from
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
index e294d56..f2d9755 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -26,7 +26,7 @@ import org.objectweb.asm.Type;
import java.util.Set;
/**
- * Class adapter that can stub some or all of the methods of the class.
+ * Class adapter that can stub some or all of the methods of the class.
*/
class TransformClassAdapter extends ClassAdapter {
@@ -41,12 +41,12 @@ class TransformClassAdapter extends ClassAdapter {
/**
* Creates a new class adapter that will stub some or all methods.
- * @param logger
- * @param stubMethods
+ * @param logger
+ * @param stubMethods list of method signatures to always stub out
* @param deleteReturns list of types that trigger the deletion of methods returning them.
* @param className The name of the class being modified
* @param cv The parent class writer visitor
- * @param stubNativesOnly True if only native methods should be stubbed. False if all
+ * @param stubNativesOnly True if only native methods should be stubbed. False if all
* methods should be stubbed.
* @param hasNative True if the method has natives, in which case its access should be
* changed.
@@ -67,10 +67,10 @@ class TransformClassAdapter extends ClassAdapter {
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
-
+
// This class might be being renamed.
name = mClassName;
-
+
// remove protected or private and set as public
access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
access |= Opcodes.ACC_PUBLIC;
@@ -82,7 +82,7 @@ class TransformClassAdapter extends ClassAdapter {
mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
super.visit(version, access, name, signature, superName, interfaces);
}
-
+
/* Visits the header of an inner class. */
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
@@ -101,7 +101,7 @@ class TransformClassAdapter extends ClassAdapter {
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
-
+
if (mDeleteReturns != null) {
Type t = Type.getReturnType(desc);
if (t.getSort() == Type.OBJECT) {
@@ -130,16 +130,16 @@ class TransformClassAdapter extends ClassAdapter {
(mStubAll ||
(access & Opcodes.ACC_NATIVE) != 0) ||
mStubMethods.contains(methodSignature)) {
-
+
boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
// remove abstract, final and native
access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
-
+
String invokeSignature = methodSignature + desc;
mLog.debug(" Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
-
+
MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature,
isStatic, isNative);
@@ -149,7 +149,7 @@ class TransformClassAdapter extends ClassAdapter {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
-
+
/* Visits a field. Makes it public. */
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature,
@@ -157,7 +157,7 @@ class TransformClassAdapter extends ClassAdapter {
// change access to public
access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
access |= Opcodes.ACC_PUBLIC;
-
+
return super.visitField(access, name, desc, signature, value);
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index 603284e..d6dba6a 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -22,7 +22,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
-import com.android.tools.layoutlib.create.LogTest.MockLog;
import org.junit.After;
import org.junit.Before;
@@ -46,9 +45,9 @@ public class AsmAnalyzerTest {
@Before
public void setUp() throws Exception {
- mLog = new LogTest.MockLog();
+ mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
+
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
@@ -69,9 +68,9 @@ public class AsmAnalyzerTest {
"mock_android.dummy.InnerTest$DerivingClass",
"mock_android.dummy.InnerTest$MyGenerics1",
"mock_android.dummy.InnerTest$MyIntEnum",
- "mock_android.dummy.InnerTest$MyStaticInnerClass",
- "mock_android.dummy.InnerTest$NotStaticInner1",
- "mock_android.dummy.InnerTest$NotStaticInner2",
+ "mock_android.dummy.InnerTest$MyStaticInnerClass",
+ "mock_android.dummy.InnerTest$NotStaticInner1",
+ "mock_android.dummy.InnerTest$NotStaticInner2",
"mock_android.view.View",
"mock_android.view.ViewGroup",
"mock_android.view.ViewGroup$LayoutParams",
@@ -83,7 +82,7 @@ public class AsmAnalyzerTest {
},
map.keySet().toArray());
}
-
+
@Test
public void testFindClass() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -91,7 +90,7 @@ public class AsmAnalyzerTest {
ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
zipClasses, found);
-
+
assertNotNull(cr);
assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName());
assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" },
@@ -172,14 +171,14 @@ public class AsmAnalyzerTest {
"mock_android.widget.TableLayout",
},
found.keySet().toArray());
-
+
for (String key : found.keySet()) {
ClassReader value = found.get(key);
assertNotNull("No value for " + key, value);
assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
}
}
-
+
@Test
public void testDependencyVisitor() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -190,7 +189,7 @@ public class AsmAnalyzerTest {
ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep);
DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
-
+
// get first level dependencies
cr.accept(visitor, 0 /* flags */);
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 7cdf79a..f4ff389 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -20,8 +20,6 @@ package com.android.tools.layoutlib.create;
import static org.junit.Assert.assertArrayEquals;
-import com.android.tools.layoutlib.create.LogTest.MockLog;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -44,9 +42,9 @@ public class AsmGeneratorTest {
@Before
public void setUp() throws Exception {
- mLog = new LogTest.MockLog();
+ mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
+
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
@@ -65,16 +63,41 @@ public class AsmGeneratorTest {
@Test
public void testClassRenaming() throws IOException, LogAbortException {
-
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar,
- null, // classes to inject in the final JAR
- null, // methods to force override
- new String[] { // classes to rename (so that we can replace them)
- "mock_android.view.View", "mock_android.view._Original_View",
- "not.an.actual.ClassName", "anoter.fake.NewClassName",
- },
- null // methods deleted from their return type.
- );
+
+ ICreateInfo ci = new ICreateInfo() {
+ public Class<?>[] getInjectedClasses() {
+ // classes to inject in the final JAR
+ return new Class<?>[0];
+ }
+
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[] {
+ "mock_android.view.View", "mock_android.view._Original_View",
+ "not.an.actual.ClassName", "anoter.fake.NewClassName",
+ };
+ }
+
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
null, // derived from
@@ -83,7 +106,7 @@ public class AsmGeneratorTest {
});
aa.analyze();
agen.generate();
-
+
Set<String> notRenamed = agen.getClassesNotRenamed();
assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
index d6916ae..0135c40 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
@@ -33,8 +33,9 @@ public class ClassHasNativeVisitorTest {
@Test
public void testHasNative() throws IOException {
MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
- ClassReader cr = new ClassReader(
- "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithNative");
+ String className =
+ this.getClass().getCanonicalName() + "$" + ClassWithNative.class.getSimpleName();
+ ClassReader cr = new ClassReader(className);
cr.accept(cv, 0 /* flags */);
assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound());
@@ -44,14 +45,17 @@ public class ClassHasNativeVisitorTest {
@Test
public void testHasNoNative() throws IOException {
MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
- ClassReader cr = new ClassReader(
- "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithoutNative");
+ String className =
+ this.getClass().getCanonicalName() + "$" + ClassWithoutNative.class.getSimpleName();
+ ClassReader cr = new ClassReader(className);
cr.accept(cv, 0 /* flags */);
assertArrayEquals(new String[0], cv.getMethodsFound());
assertFalse(cv.hasNativeMethods());
}
+ //-------
+
/**
* Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
*/
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
new file mode 100644
index 0000000..7d80796
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+
+public class DelegateClassAdapterTest {
+
+ private MockLog mLog;
+
+ private static final String CLASS_NAME =
+ DelegateClassAdapterTest.class.getCanonicalName() + "$" +
+ ClassWithNative.class.getSimpleName();
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ mLog.setVerbose(true); // capture debug error too
+ }
+
+ /**
+ * Tests that a class not being modified still works.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testNoOp() throws Exception {
+ // create an instance of the class that will be modified
+ // (load the class in a distinct class loader so that we can trash its definition later)
+ ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
+ Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(CLASS_NAME);
+ ClassWithNative instance1 = clazz1.newInstance();
+ assertEquals(42, instance1.add(20, 22));
+ try {
+ instance1.callNativeInstance(10, 3.1415, new Object[0] );
+ fail("Test should have failed to invoke callTheNativeMethod [1]");
+ } catch (UnsatisfiedLinkError e) {
+ // This is expected to fail since the native method is not implemented.
+ }
+
+ // Now process it but tell the delegate to not modify any method
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ String internalClassName = CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+
+ // Load the generated class in a different class loader and try it again
+ final byte[] bytes = cw.toByteArray();
+
+ ClassLoader2 cl2 = new ClassLoader2(bytes) {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+ assertEquals(42, callAdd(i2, 20, 22));
+
+ try {
+ callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
+ fail("Test should have failed to invoke callTheNativeMethod [2]");
+ } catch (InvocationTargetException e) {
+ // This is expected to fail since the native method has NOT been
+ // overridden here.
+ assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
+ }
+
+ // Check that the native method does NOT have the new annotation
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertTrue(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals(0, a.length);
+ }
+ };
+ cl2.testModifiedInstance();
+ }
+
+ /**
+ * {@link DelegateMethodAdapter} does not support overriding constructors yet,
+ * so this should fail with an {@link UnsupportedOperationException}.
+ *
+ * Although not tested here, the message of the exception should contain the
+ * constructor signature.
+ */
+ @Test(expected=UnsupportedOperationException.class)
+ public void testConstructorsNotSupported() throws IOException {
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+ String internalClassName = CLASS_NAME.replace('.', '/');
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add("<init>");
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+ }
+
+ @Test
+ public void testDelegateNative() throws Exception {
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+ String internalClassName = CLASS_NAME.replace('.', '/');
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+
+ // Load the generated class in a different class loader and try it
+ final byte[] bytes = cw.toByteArray();
+
+ try {
+ ClassLoader2 cl2 = new ClassLoader2(bytes) {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+
+ // Use reflection to access inner methods
+ assertEquals(42, callAdd(i2, 20, 22));
+
+ Object[] objResult = new Object[] { null };
+ int result = callCallNativeInstance(i2, 10, 3.1415, objResult);
+ assertEquals((int)(10 + 3.1415), result);
+ assertSame(i2, objResult[0]);
+
+ // Check that the native method now has the new annotation and is not native
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertFalse(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
+ }
+ };
+ cl2.testModifiedInstance();
+
+ // This code block is useful for debugging. However to make it work you need to
+ // pull in the org.objectweb.asm.util.TraceClassVisitor class and associated
+ // utilities which are found in the ASM source jar.
+ //
+ // } catch (Throwable t) {
+ // For debugging, dump the bytecode of the class in case of unexpected error.
+ // StringWriter sw = new StringWriter();
+ // PrintWriter pw = new PrintWriter(sw);
+ // TraceClassVisitor tcv = new TraceClassVisitor(pw);
+ // ClassReader cr2 = new ClassReader(bytes);
+ // cr2.accept(tcv, 0 /* flags */);
+ // String msg = "\n" + t.getClass().getCanonicalName();
+ // if (t.getMessage() != null) {
+ // msg += ": " + t.getMessage();
+ // }
+ // msg = msg + "\nBytecode dump:\n" + sw.toString();
+ // // Re-throw exception with new message
+ // RuntimeException ex = new RuntimeException(msg, t);
+ // throw ex;
+ } finally {
+ }
+ }
+
+ //-------
+
+ /**
+ * A class loader than can define and instantiate our dummy {@link ClassWithNative}.
+ * <p/>
+ * The trick here is that this class loader will test our modified version of ClassWithNative.
+ * Trying to do so in the original class loader generates all sort of link issues because
+ * there are 2 different definitions of the same class name. This class loader will
+ * define and load the class when requested by name and provide helpers to access the
+ * instance methods via reflection.
+ */
+ private abstract class ClassLoader2 extends ClassLoader {
+ private final byte[] mClassWithNative;
+
+ public ClassLoader2(byte[] classWithNative) {
+ super(null);
+ mClassWithNative = classWithNative;
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ try {
+ return super.findClass(name);
+ } catch (ClassNotFoundException e) {
+
+ if (CLASS_NAME.equals(name)) {
+ // Load the modified ClassWithNative from its bytes representation.
+ return defineClass(CLASS_NAME, mClassWithNative, 0, mClassWithNative.length);
+ }
+
+ try {
+ // Load everything else from the original definition into the new class loader.
+ ClassReader cr = new ClassReader(name);
+ ClassWriter cw = new ClassWriter(0);
+ cr.accept(cw, 0);
+ byte[] bytes = cw.toByteArray();
+ return defineClass(name, bytes, 0, bytes.length);
+
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ }
+
+ /**
+ * Accesses {@link ClassWithNative#add(int, int)} via reflection.
+ */
+ public int callAdd(Object instance, int a, int b) throws Exception {
+ Method m = instance.getClass().getMethod("add",
+ new Class<?>[] { int.class, int.class });
+
+ Object result = m.invoke(instance, new Object[] { a, b });
+ return ((Integer) result).intValue();
+ }
+
+ /**
+ * Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])}
+ * via reflection.
+ */
+ public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
+ throws Exception {
+ Method m = instance.getClass().getMethod("callNativeInstance",
+ new Class<?>[] { int.class, double.class, Object[].class });
+
+ Object result = m.invoke(instance, new Object[] { a, d, o });
+ return ((Integer) result).intValue();
+ }
+
+ public abstract void testModifiedInstance() throws Exception;
+ }
+
+ /**
+ * Dummy test class with a native method.
+ * The native method is not defined and any attempt to invoke it will
+ * throw an {@link UnsatisfiedLinkError}.
+ */
+ public static class ClassWithNative {
+ public ClassWithNative() {
+ }
+
+ public int add(int a, int b) {
+ return a + b;
+ }
+
+ public int callNativeInstance(int a, double d, Object[] o) {
+ return native_instance(a, d, o);
+ }
+
+ private native int native_instance(int a, double d, Object[] o);
+ }
+
+ /**
+ * The delegate that receives the call to {@link ClassWithNative}'s overridden methods.
+ */
+ public static class ClassWithNative_Delegate {
+ public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
+ if (o != null && o.length > 0) {
+ o[0] = instance;
+ }
+ return (int)(a + d);
+ }
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
index 3f13158..1a5f653 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
@@ -24,33 +24,8 @@ import org.junit.Test;
public class LogTest {
- public static class MockLog extends Log {
- StringBuilder mOut = new StringBuilder();
- StringBuilder mErr = new StringBuilder();
-
- public String getOut() {
- return mOut.toString();
- }
-
- public String getErr() {
- return mErr.toString();
- }
-
- @Override
- protected void outPrintln(String msg) {
- mOut.append(msg);
- mOut.append('\n');
- }
-
- @Override
- protected void errPrintln(String msg) {
- mErr.append(msg);
- mErr.append('\n');
- }
- }
-
private MockLog mLog;
-
+
@Before
public void setUp() throws Exception {
mLog = new MockLog();
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
new file mode 100644
index 0000000..de750a3
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+
+public class MockLog extends Log {
+ StringBuilder mOut = new StringBuilder();
+ StringBuilder mErr = new StringBuilder();
+
+ public String getOut() {
+ return mOut.toString();
+ }
+
+ public String getErr() {
+ return mErr.toString();
+ }
+
+ @Override
+ protected void outPrintln(String msg) {
+ mOut.append(msg);
+ mOut.append('\n');
+ }
+
+ @Override
+ protected void errPrintln(String msg) {
+ mErr.append(msg);
+ mErr.append('\n');
+ }
+}