diff options
Diffstat (limited to 'tools')
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><className>_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 <bunch of framework jars> + * $ 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'); + } +} |