diff options
author | Xavier Ducrohet <xav@android.com> | 2011-02-07 21:08:10 -0800 |
---|---|---|
committer | Xavier Ducrohet <xav@android.com> | 2011-02-23 19:47:02 -0800 |
commit | 4b52ec49fee79b0488d6a9eaaa4ea5d74ce90905 (patch) | |
tree | 5b37f5bc58ed3bacbc8536aabcc7e08a57776801 /tools | |
parent | 2050de5b9a5c097e30545ab822211d49031bd9dc (diff) | |
download | frameworks_base-4b52ec49fee79b0488d6a9eaaa4ea5d74ce90905.zip frameworks_base-4b52ec49fee79b0488d6a9eaaa4ea5d74ce90905.tar.gz frameworks_base-4b52ec49fee79b0488d6a9eaaa4ea5d74ce90905.tar.bz2 |
LayoutLib: Original import of Honeycomb's layoutlib. do not merge.
frameworks/base.git @ f0a53435f14d23d9555fc46014352ee6a7baa647
Change-Id: Ibc215751693dc7650683b61bb458f7c8beaf8060
Diffstat (limited to 'tools')
141 files changed, 17623 insertions, 8401 deletions
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 175a98b..5cfda29 100644 --- a/tools/layoutlib/bridge/.classpath +++ b/tools/layoutlib/bridge/.classpath @@ -4,9 +4,10 @@ <classpathentry kind="src" path="tests"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> - <classpathentry combineaccessrules="false" kind="src" path="/layoutlib_api"/> - <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_OUT_FRAMEWORK/ninepatch.jar" sourcepath="/ANDROID_SRC/development/tools/ninepatch/src"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilt/common/layoutlib_api/layoutlib_api-prebuilt.jar"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilt/common/ninepatch/ninepatch-prebuilt.jar"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilt/common/tools-common/tools-common-prebuilt.jar"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk index b2010d5..7e70f33 100644 --- a/tools/layoutlib/bridge/Android.mk +++ b/tools/layoutlib/bridge/Android.mk @@ -17,13 +17,17 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_JAVA_RESOURCE_DIRS := resources + LOCAL_JAVA_LIBRARIES := \ kxml2-2.3.0 \ - layoutlib_api \ - ninepatch + layoutlib_api-prebuilt \ + tools-common-prebuilt -LOCAL_STATIC_JAVA_LIBRARIES := temp_layoutlib +LOCAL_STATIC_JAVA_LIBRARIES := \ + temp_layoutlib \ + ninepatch-prebuilt LOCAL_MODULE := layoutlib diff --git a/tools/layoutlib/bridge/resources/bars/action_bar.xml b/tools/layoutlib/bridge/resources/bars/action_bar.xml new file mode 100644 index 0000000..51983f2 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/action_bar.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> +</merge> diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..bd44b52 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png Binary files differnew file mode 100644 index 0000000..a4be298 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back_default.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back_default.png Binary files differnew file mode 100644 index 0000000..4bcd2be --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back_default.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home_default.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home_default.png Binary files differnew file mode 100644 index 0000000..cfeba3e --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home_default.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent_default.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent_default.png Binary files differnew file mode 100644 index 0000000..1d97e05 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent_default.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..c629387 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png Binary files differnew file mode 100644 index 0000000..eb7c1a4 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png diff --git a/tools/layoutlib/bridge/resources/bars/phone_system_bar.xml b/tools/layoutlib/bridge/resources/bars/phone_system_bar.xml new file mode 100644 index 0000000..d3c492e --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/phone_system_bar.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_marginLeft="3dip" + android:layout_marginRight="5dip"/> +</merge> diff --git a/tools/layoutlib/bridge/resources/bars/tablet_system_bar.xml b/tools/layoutlib/bridge/resources/bars/tablet_system_bar.xml new file mode 100644 index 0000000..c5acddb --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/tablet_system_bar.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_marginLeft="3dip" + android:layout_marginRight="15dip"/> +</merge> diff --git a/tools/layoutlib/bridge/resources/bars/title_bar.xml b/tools/layoutlib/bridge/resources/bars/title_bar.xml new file mode 100644 index 0000000..76d78d9 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/title_bar.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> +</merge> diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java new file mode 100644 index 0000000..7b444aa --- /dev/null +++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java @@ -0,0 +1,59 @@ +/* + * 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.animation; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.animation.PropertyValuesHolder + * + * Through the layoutlib_create tool, the original native methods of PropertyValuesHolder have been + * replaced by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + * The main goal of this class' methods are to provide a native way to access setters and getters + * on some object. In this case we want to default to using Java reflection instead so the native + * methods do nothing. + * + */ +/*package*/ class PropertyValuesHolder_Delegate { + + @LayoutlibDelegate + /*package*/ static int nGetIntMethod(Class<?> targetClass, String methodName) { + // return 0 to force PropertyValuesHolder to use Java reflection. + return 0; + } + + @LayoutlibDelegate + /*package*/ static int nGetFloatMethod(Class<?> targetClass, String methodName) { + // return 0 to force PropertyValuesHolder to use Java reflection. + return 0; + } + + @LayoutlibDelegate + /*package*/ static void nCallIntMethod(Object target, int methodID, int arg) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallFloatMethod(Object target, int methodID, float arg) { + // do nothing + } +} diff --git a/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java new file mode 100644 index 0000000..aabd3f1 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java @@ -0,0 +1,104 @@ +/* + * 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.app; + +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.Context; +import android.os.Bundle; + +/** + * Delegate used to provide new implementation of a select few methods of {@link Fragment} + * + * Through the layoutlib_create tool, the original methods of Fragment have been replaced + * by calls to methods of the same name in this delegate class. + * + * The methods being re-implemented are the ones responsible for instantiating Fragment objects. + * Because the classes of these objects are found in the project, these methods need access to + * {@link IProjectCallback} object. They are however static methods, so the callback is set + * before the inflation through {@link #setProjectCallback(IProjectCallback)}. + */ +public class Fragment_Delegate { + + private static IProjectCallback sProjectCallback; + + /** + * Sets the current {@link IProjectCallback} to be used to instantiate classes coming + * from the project being rendered. + */ + public static void setProjectCallback(IProjectCallback projectCallback) { + sProjectCallback = projectCallback; + } + + /** + * Like {@link #instantiate(Context, String, Bundle)} but with a null + * argument Bundle. + */ + @LayoutlibDelegate + /*package*/ static Fragment instantiate(Context context, String fname) { + return instantiate(context, fname, null); + } + + /** + * Create a new instance of a Fragment with the given class name. This is + * the same as calling its empty constructor. + * + * @param context The calling context being used to instantiate the fragment. + * This is currently just used to get its ClassLoader. + * @param fname The class name of the fragment to instantiate. + * @param args Bundle of arguments to supply to the fragment, which it + * can retrieve with {@link #getArguments()}. May be null. + * @return Returns a new fragment instance. + * @throws InstantiationException If there is a failure in instantiating + * the given fragment class. This is a runtime exception; it is not + * normally expected to happen. + */ + @LayoutlibDelegate + /*package*/ static Fragment instantiate(Context context, String fname, Bundle args) { + try { + if (sProjectCallback != null) { + Fragment f = (Fragment) sProjectCallback.loadView(fname, + new Class[0], new Object[0]); + + if (args != null) { + args.setClassLoader(f.getClass().getClassLoader()); + f.mArguments = args; + } + return f; + } + + return null; + } catch (ClassNotFoundException e) { + throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); + } catch (java.lang.InstantiationException e) { + throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); + } catch (IllegalAccessException e) { + throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); + } catch (Exception e) { + throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); + } + } +} diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java new file mode 100644 index 0000000..413894b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011 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.content.res; + +import com.android.layoutlib.bridge.impl.RenderSessionImpl; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.res.Resources.NotFoundException; +import android.content.res.Resources.Theme; +import android.util.AttributeSet; +import android.util.TypedValue; + +/** + * Delegate used to provide new implementation of a select few methods of {@link Theme} + * + * Through the layoutlib_create tool, the original methods of Theme have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class Resources_Theme_Delegate { + + @LayoutlibDelegate + /*package*/ static TypedArray obtainStyledAttributes( + Resources thisResources, Theme thisTheme, + int[] attrs) { + return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs); + } + + @LayoutlibDelegate + /*package*/ static TypedArray obtainStyledAttributes( + Resources thisResources, Theme thisTheme, + int resid, int[] attrs) + throws NotFoundException { + return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs); + } + + @LayoutlibDelegate + /*package*/ static TypedArray obtainStyledAttributes( + Resources thisResources, Theme thisTheme, + AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { + return RenderSessionImpl.getCurrentContext().obtainStyledAttributes( + set, attrs, defStyleAttr, defStyleRes); + } + + @LayoutlibDelegate + /*package*/ static boolean resolveAttribute( + Resources thisResources, Theme thisTheme, + int resid, TypedValue outValue, + boolean resolveRefs) { + return RenderSessionImpl.getCurrentContext().resolveThemeAttribute( + resid, outValue, resolveRefs); + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java new file mode 100644 index 0000000..a50a2bd --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java @@ -0,0 +1,70 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.AvoidXfermode + * + * Through the layoutlib_create tool, the original native methods of AvoidXfermode 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 AvoidXfermode class. + * + * Because this extends {@link Xfermode_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link Xfermode_Delegate}. + * + */ +public class AvoidXfermode_Delegate extends Xfermode_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Composite getComposite(int alpha) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Avoid Xfermodes are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int opColor, int tolerance, int nativeMode) { + AvoidXfermode_Delegate newDelegate = new AvoidXfermode_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java deleted file mode 100644 index 35f022e..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java +++ /dev/null @@ -1,278 +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.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -import javax.imageio.ImageIO; - -public final class Bitmap extends _Original_Bitmap { - - private BufferedImage mImage; - - public Bitmap(File input) throws IOException { - super(1, true, null, -1); - - mImage = ImageIO.read(input); - } - - public Bitmap(InputStream is) throws IOException { - super(1, true, null, -1); - - mImage = ImageIO.read(is); - } - - Bitmap(BufferedImage image) { - super(1, true, null, -1); - mImage = image; - } - - public BufferedImage getImage() { - return mImage; - } - - // ----- overriden methods - - public enum Config { - // these native values must match up with the enum in SkBitmap.h - ALPHA_8 (2), - RGB_565 (4), - ARGB_4444 (5), - ARGB_8888 (6); - - Config(int ni) { - this.nativeInt = ni; - } - final int nativeInt; - - /* package */ static Config nativeToConfig(int ni) { - return sConfigs[ni]; - } - - private static Config sConfigs[] = { - null, null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888 - }; - } - - @Override - public int getWidth() { - return mImage.getWidth(); - } - - @Override - public int getHeight() { - return mImage.getHeight(); - } - - /** - * Returns an immutable bitmap from the source bitmap. The new bitmap may - * be the same object as source, or a copy may have been made. - */ - public static Bitmap createBitmap(Bitmap src) { - return createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), null, false); - } - - /** - * Returns an immutable bitmap from the specified subset of the source - * bitmap. The new bitmap may be the same object as source, or a copy may - * have been made. - * - * @param source The bitmap we are subsetting - * @param x The x coordinate of the first pixel in source - * @param y The y coordinate of the first pixel in source - * @param width The number of pixels in each row - * @param height The number of rows - */ - public static Bitmap createBitmap(Bitmap source, int x, int y, - int width, int height) { - return new Bitmap(source.mImage.getSubimage(x, y, width, height)); - } - - /** - * Returns an immutable bitmap from subset of the source bitmap, - * transformed by the optional matrix. - * - * @param source The bitmap we are subsetting - * @param x The x coordinate of the first pixel in source - * @param y The y coordinate of the first pixel in source - * @param width The number of pixels in each row - * @param height The number of rows - * @param m Option matrix to be applied to the pixels - * @param filter true if the source should be filtered. - * Only applies if the matrix contains more than just - * translation. - * @return A bitmap that represents the specified subset of source - * @throws IllegalArgumentException if the x, y, width, height values are - * outside of the dimensions of the source bitmap. - */ - public static Bitmap createBitmap(Bitmap source, int x, int y, int width, - int height, Matrix m, boolean filter) { - checkXYSign(x, y); - checkWidthHeight(width, height); - if (x + width > source.getWidth()) { - throw new IllegalArgumentException( - "x + width must be <= bitmap.width()"); - } - if (y + height > source.getHeight()) { - throw new IllegalArgumentException( - "y + height must be <= bitmap.height()"); - } - - // check if we can just return our argument unchanged - if (!source.isMutable() && x == 0 && y == 0 - && width == source.getWidth() && height == source.getHeight() - && (m == null || m.isIdentity())) { - return source; - } - - if (m == null || m.isIdentity()) { - return new Bitmap(source.mImage.getSubimage(x, y, width, height)); - } - - int neww = width; - int newh = height; - Paint paint; - - Rect srcR = new Rect(x, y, x + width, y + height); - RectF dstR = new RectF(0, 0, width, height); - - /* the dst should have alpha if the src does, or if our matrix - doesn't preserve rectness - */ - boolean hasAlpha = source.hasAlpha() || !m.rectStaysRect(); - RectF deviceR = new RectF(); - m.mapRect(deviceR, dstR); - neww = Math.round(deviceR.width()); - newh = Math.round(deviceR.height()); - - Canvas canvas = new Canvas(neww, newh); - - canvas.translate(-deviceR.left, -deviceR.top); - canvas.concat(m); - paint = new Paint(); - paint.setFilterBitmap(filter); - if (!m.rectStaysRect()) { - paint.setAntiAlias(true); - } - - canvas.drawBitmap(source, srcR, dstR, paint); - - return new Bitmap(canvas.getImage()); - } - - /** - * Returns a mutable bitmap with the specified width and height. - * - * @param width The width of the bitmap - * @param height The height of the bitmap - * @param config The bitmap config to create. - * @throws IllegalArgumentException if the width or height are <= 0 - */ - public static Bitmap createBitmap(int width, int height, Config config) { - return new Bitmap(new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)); - } - - /** - * Returns a immutable bitmap with the specified width and height, with each - * pixel value set to the corresponding value in the colors array. - * - * @param colors Array of {@link Color} used to initialize the pixels. - * @param offset Number of values to skip before the first color in the - * array of colors. - * @param stride Number of colors in the array between rows (must be >= - * width or <= -width). - * @param width The width of the bitmap - * @param height The height of the bitmap - * @param config The bitmap config to create. If the config does not - * support per-pixel alpha (e.g. RGB_565), then the alpha - * bytes in the colors[] will be ignored (assumed to be FF) - * @throws IllegalArgumentException if the width or height are <= 0, or if - * the color array's length is less than the number of pixels. - */ - public static Bitmap createBitmap(int colors[], int offset, int stride, - int width, int height, Config config) { - checkWidthHeight(width, height); - if (Math.abs(stride) < width) { - throw new IllegalArgumentException("abs(stride) must be >= width"); - } - int lastScanline = offset + (height - 1) * stride; - int length = colors.length; - if (offset < 0 || (offset + width > length) - || lastScanline < 0 - || (lastScanline + width > length)) { - throw new ArrayIndexOutOfBoundsException(); - } - - // TODO: create an immutable bitmap... - throw new UnsupportedOperationException(); - } - - /** - * Returns a immutable bitmap with the specified width and height, with each - * pixel value set to the corresponding value in the colors array. - * - * @param colors Array of {@link Color} used to initialize the pixels. - * This array must be at least as large as width * height. - * @param width The width of the bitmap - * @param height The height of the bitmap - * @param config The bitmap config to create. If the config does not - * support per-pixel alpha (e.g. RGB_565), then the alpha - * bytes in the colors[] will be ignored (assumed to be FF) - * @throws IllegalArgumentException if the width or height are <= 0, or if - * the color array's length is less than the number of pixels. - */ - public static Bitmap createBitmap(int colors[], int width, int height, - Config config) { - return createBitmap(colors, 0, width, width, height, config); - } - - public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, - int dstHeight, boolean filter) { - Matrix m; - synchronized (Bitmap.class) { - // small pool of just 1 matrix - m = sScaleMatrix; - sScaleMatrix = null; - } - - if (m == null) { - m = new Matrix(); - } - - final int width = src.getWidth(); - final int height = src.getHeight(); - final float sx = dstWidth / (float)width; - final float sy = dstHeight / (float)height; - m.setScale(sx, sy); - Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter); - - synchronized (Bitmap.class) { - // do we need to check for null? why not just assign everytime? - if (sScaleMatrix == null) { - sScaleMatrix = m; - } - } - - return b; - } - - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java deleted file mode 100644 index e978fe8..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java +++ /dev/null @@ -1,566 +0,0 @@ -/* - * 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 android.content.res.AssetManager; -import android.content.res.Resources; -import android.util.DisplayMetrics; -import android.util.TypedValue; - -import java.io.BufferedInputStream; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Creates Bitmap objects from various sources, including files, streams, - * and byte-arrays. - */ -public class BitmapFactory { - public static class Options { - /** - * Create a default Options object, which if left unchanged will give - * the same result from the decoder as if null were passed. - */ - public Options() { - inDither = true; - inScaled = true; - } - - /** - * If set to true, the decoder will return null (no bitmap), but - * the out... fields will still be set, allowing the caller to query - * the bitmap without having to allocate the memory for its pixels. - */ - public boolean inJustDecodeBounds; - - /** - * If set to a value > 1, requests the decoder to subsample the original - * image, returning a smaller image to save memory. The sample size is - * the number of pixels in either dimension that correspond to a single - * pixel in the decoded bitmap. For example, inSampleSize == 4 returns - * an image that is 1/4 the width/height of the original, and 1/16 the - * number of pixels. Any value <= 1 is treated the same as 1. Note: the - * decoder will try to fulfill this request, but the resulting bitmap - * may have different dimensions that precisely what has been requested. - * Also, powers of 2 are often faster/easier for the decoder to honor. - */ - public int inSampleSize; - - /** - * If this is non-null, the decoder will try to decode into this - * internal configuration. If it is null, or the request cannot be met, - * the decoder will try to pick the best matching config based on the - * system's screen depth, and characteristics of the original image such - * as if it has per-pixel alpha (requiring a config that also does). - */ - public Bitmap.Config inPreferredConfig; - - /** - * If dither is true, the decoder will attempt to dither the decoded - * image. - */ - public boolean inDither; - - /** - * The pixel density to use for the bitmap. This will always result - * in the returned bitmap having a density set for it (see - * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)). In addition, - * if {@link #inScaled} is set (which it is by default} and this - * density does not match {@link #inTargetDensity}, then the bitmap - * will be scaled to the target density before being returned. - * - * <p>If this is 0, - * {@link BitmapFactory#decodeResource(Resources, int)}, - * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, - * and {@link BitmapFactory#decodeResourceStream} - * will fill in the density associated with the resource. The other - * functions will leave it as-is and no density will be applied. - * - * @see #inTargetDensity - * @see #inScreenDensity - * @see #inScaled - * @see Bitmap#setDensity(int) - * @see android.util.DisplayMetrics#densityDpi - */ - public int inDensity; - - /** - * The pixel density of the destination this bitmap will be drawn to. - * This is used in conjunction with {@link #inDensity} and - * {@link #inScaled} to determine if and how to scale the bitmap before - * returning it. - * - * <p>If this is 0, - * {@link BitmapFactory#decodeResource(Resources, int)}, - * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, - * and {@link BitmapFactory#decodeResourceStream} - * will fill in the density associated the Resources object's - * DisplayMetrics. The other - * functions will leave it as-is and no scaling for density will be - * performed. - * - * @see #inDensity - * @see #inScreenDensity - * @see #inScaled - * @see android.util.DisplayMetrics#densityDpi - */ - public int inTargetDensity; - - /** - * The pixel density of the actual screen that is being used. This is - * purely for applications running in density compatibility code, where - * {@link #inTargetDensity} is actually the density the application - * sees rather than the real screen density. - * - * <p>By setting this, you - * allow the loading code to avoid scaling a bitmap that is currently - * in the screen density up/down to the compatibility density. Instead, - * if {@link #inDensity} is the same as {@link #inScreenDensity}, the - * bitmap will be left as-is. Anything using the resulting bitmap - * must also used {@link Bitmap#getScaledWidth(int) - * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight - * Bitmap.getScaledHeight} to account for any different between the - * bitmap's density and the target's density. - * - * <p>This is never set automatically for the caller by - * {@link BitmapFactory} itself. It must be explicitly set, since the - * caller must deal with the resulting bitmap in a density-aware way. - * - * @see #inDensity - * @see #inTargetDensity - * @see #inScaled - * @see android.util.DisplayMetrics#densityDpi - */ - public int inScreenDensity; - - /** - * When this flag is set, if {@link #inDensity} and - * {@link #inTargetDensity} are not 0, the - * bitmap will be scaled to match {@link #inTargetDensity} when loaded, - * rather than relying on the graphics system scaling it each time it - * is drawn to a Canvas. - * - * <p>This flag is turned on by default and should be turned off if you need - * a non-scaled version of the bitmap. Nine-patch bitmaps ignore this - * flag and are always scaled. - */ - public boolean inScaled; - - /** - * If this is set to true, then the resulting bitmap will allocate its - * pixels such that they can be purged if the system needs to reclaim - * memory. In that instance, when the pixels need to be accessed again - * (e.g. the bitmap is drawn, getPixels() is called), they will be - * automatically re-decoded. - * - * For the re-decode to happen, the bitmap must have access to the - * encoded data, either by sharing a reference to the input - * or by making a copy of it. This distinction is controlled by - * inInputShareable. If this is true, then the bitmap may keep a shallow - * reference to the input. If this is false, then the bitmap will - * explicitly make a copy of the input data, and keep that. Even if - * sharing is allowed, the implementation may still decide to make a - * deep copy of the input data. - */ - public boolean inPurgeable; - - /** - * This field works in conjuction with inPurgeable. If inPurgeable is - * false, then this field is ignored. If inPurgeable is true, then this - * field determines whether the bitmap can share a reference to the - * input data (inputstream, array, etc.) or if it must make a deep copy. - */ - public boolean inInputShareable; - - /** - * Normally bitmap allocations count against the dalvik heap, which - * means they help trigger GCs when a lot have been allocated. However, - * in rare cases, the caller may want to allocate the bitmap outside of - * that heap. To request that, set inNativeAlloc to true. In these - * rare instances, it is solely up to the caller to ensure that OOM is - * managed explicitly by calling bitmap.recycle() as soon as such a - * bitmap is no longer needed. - * - * @hide pending API council approval - */ - public boolean inNativeAlloc; - - /** - * The resulting width of the bitmap, set independent of the state of - * inJustDecodeBounds. However, if there is an error trying to decode, - * outWidth will be set to -1. - */ - public int outWidth; - - /** - * The resulting height of the bitmap, set independent of the state of - * inJustDecodeBounds. However, if there is an error trying to decode, - * outHeight will be set to -1. - */ - public int outHeight; - - /** - * If known, this string is set to the mimetype of the decoded image. - * If not know, or there is an error, it is set to null. - */ - public String outMimeType; - - /** - * Temp storage to use for decoding. Suggest 16K or so. - */ - public byte[] inTempStorage; - - private native void requestCancel(); - - /** - * Flag to indicate that cancel has been called on this object. This - * is useful if there's an intermediary that wants to first decode the - * bounds and then decode the image. In that case the intermediary - * can check, inbetween the bounds decode and the image decode, to see - * if the operation is canceled. - */ - public boolean mCancel; - - /** - * This can be called from another thread while this options object is - * inside a decode... call. Calling this will notify the decoder that - * it should cancel its operation. This is not guaranteed to cancel - * the decode, but if it does, the decoder... operation will return - * null, or if inJustDecodeBounds is true, will set outWidth/outHeight - * to -1 - */ - public void requestCancelDecode() { - mCancel = true; - requestCancel(); - } - } - - /** - * Decode a file path into a bitmap. If the specified file name is null, - * or cannot be decoded into a bitmap, the function returns null. - * - * @param pathName complete path name for the file to be decoded. - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeFile(String pathName, Options opts) { - Bitmap bm = null; - InputStream stream = null; - try { - stream = new FileInputStream(pathName); - bm = decodeStream(stream, null, opts); - } catch (Exception e) { - /* do nothing. - If the exception happened on open, bm will be null. - */ - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - // do nothing here - } - } - } - return bm; - } - - /** - * Decode a file path into a bitmap. If the specified file name is null, - * or cannot be decoded into a bitmap, the function returns null. - * - * @param pathName complete path name for the file to be decoded. - * @return the resulting decoded bitmap, or null if it could not be decoded. - */ - public static Bitmap decodeFile(String pathName) { - return decodeFile(pathName, null); - } - - /** - * Decode a new Bitmap from an InputStream. This InputStream was obtained from - * resources, which we pass to be able to scale the bitmap accordingly. - */ - public static Bitmap decodeResourceStream(Resources res, TypedValue value, - InputStream is, Rect pad, Options opts) { - - if (opts == null) { - opts = new Options(); - } - - if (opts.inDensity == 0 && value != null) { - final int density = value.density; - if (density == TypedValue.DENSITY_DEFAULT) { - opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; - } else if (density != TypedValue.DENSITY_NONE) { - opts.inDensity = density; - } - } - - if (opts.inTargetDensity == 0 && res != null) { - opts.inTargetDensity = res.getDisplayMetrics().densityDpi; - } - - return decodeStream(is, pad, opts); - } - - /** - * Synonym for opening the given resource and calling - * {@link #decodeResourceStream}. - * - * @param res The resources object containing the image data - * @param id The resource id of the image data - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeResource(Resources res, int id, Options opts) { - Bitmap bm = null; - InputStream is = null; - - try { - final TypedValue value = new TypedValue(); - is = res.openRawResource(id, value); - - bm = decodeResourceStream(res, value, is, null, opts); - } catch (Exception e) { - /* do nothing. - If the exception happened on open, bm will be null. - If it happened on close, bm is still valid. - */ - } finally { - try { - if (is != null) is.close(); - } catch (IOException e) { - // Ignore - } - } - - return bm; - } - - /** - * Synonym for {@link #decodeResource(Resources, int, android.graphics.BitmapFactory.Options)} - * will null Options. - * - * @param res The resources object containing the image data - * @param id The resource id of the image data - * @return The decoded bitmap, or null if the image could not be decode. - */ - public static Bitmap decodeResource(Resources res, int id) { - return decodeResource(res, id, null); - } - - /** - * Decode an immutable bitmap from the specified byte array. - * - * @param data byte array of compressed image data - * @param offset offset into imageData for where the decoder should begin - * parsing. - * @param length the number of bytes, beginning at offset, to parse - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) { - if ((offset | length) < 0 || data.length < offset + length) { - throw new ArrayIndexOutOfBoundsException(); - } - - // FIXME: implement as needed, but it's unlikely that this is needed in the context of the bridge. - return null; - //return nativeDecodeByteArray(data, offset, length, opts); - } - - /** - * Decode an immutable bitmap from the specified byte array. - * - * @param data byte array of compressed image data - * @param offset offset into imageData for where the decoder should begin - * parsing. - * @param length the number of bytes, beginning at offset, to parse - * @return The decoded bitmap, or null if the image could not be decode. - */ - public static Bitmap decodeByteArray(byte[] data, int offset, int length) { - return decodeByteArray(data, offset, length, null); - } - - /** - * Decode an input stream into a bitmap. If the input stream is null, or - * cannot be used to decode a bitmap, the function returns null. - * The stream's position will be where ever it was after the encoded data - * was read. - * - * @param is The input stream that holds the raw data to be decoded into a - * bitmap. - * @param outPadding If not null, return the padding rect for the bitmap if - * it exists, otherwise set padding to [-1,-1,-1,-1]. If - * no bitmap is returned (null) then padding is - * unchanged. - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { - // we don't throw in this case, thus allowing the caller to only check - // the cache, and not force the image to be decoded. - if (is == null) { - return null; - } - - // we need mark/reset to work properly - - if (!is.markSupported()) { - is = new BufferedInputStream(is, 16 * 1024); - } - - // so we can call reset() if a given codec gives up after reading up to - // this many bytes. FIXME: need to find out from the codecs what this - // value should be. - is.mark(1024); - - Bitmap bm; - - if (is instanceof AssetManager.AssetInputStream) { - // FIXME: log this. - return null; - } else { - // pass some temp storage down to the native code. 1024 is made up, - // but should be large enough to avoid too many small calls back - // into is.read(...) This number is not related to the value passed - // to mark(...) above. - try { - bm = new Bitmap(is); - } catch (IOException e) { - return null; - } - } - - return finishDecode(bm, outPadding, opts); - } - - private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { - if (bm == null || opts == null) { - return bm; - } - - final int density = opts.inDensity; - if (density == 0) { - return bm; - } - - bm.setDensity(density); - final int targetDensity = opts.inTargetDensity; - if (targetDensity == 0 || density == targetDensity - || density == opts.inScreenDensity) { - return bm; - } - - byte[] np = bm.getNinePatchChunk(); - final boolean isNinePatch = false; //np != null && NinePatch.isNinePatchChunk(np); - if (opts.inScaled || isNinePatch) { - float scale = targetDensity / (float)density; - // TODO: This is very inefficient and should be done in native by Skia - final Bitmap oldBitmap = bm; - bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f), - (int) (bm.getHeight() * scale + 0.5f), true); - oldBitmap.recycle(); - - if (isNinePatch) { - //np = nativeScaleNinePatch(np, scale, outPadding); - bm.setNinePatchChunk(np); - } - bm.setDensity(targetDensity); - } - - return bm; - } - - /** - * Decode an input stream into a bitmap. If the input stream is null, or - * cannot be used to decode a bitmap, the function returns null. - * The stream's position will be where ever it was after the encoded data - * was read. - * - * @param is The input stream that holds the raw data to be decoded into a - * bitmap. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeStream(InputStream is) { - return decodeStream(is, null, null); - } - - /** - * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded - * return null. The position within the descriptor will not be changed when - * this returns, so the descriptor can be used again as-is. - * - * @param fd The file descriptor containing the bitmap data to decode - * @param outPadding If not null, return the padding rect for the bitmap if - * it exists, otherwise set padding to [-1,-1,-1,-1]. If - * no bitmap is returned (null) then padding is - * unchanged. - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return the decoded bitmap, or null - */ - public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) { - return null; - - /* FIXME: implement as needed - try { - if (MemoryFile.isMemoryFile(fd)) { - int mappedlength = MemoryFile.getMappedSize(fd); - MemoryFile file = new MemoryFile(fd, mappedlength, "r"); - InputStream is = file.getInputStream(); - Bitmap bm = decodeStream(is, outPadding, opts); - return finishDecode(bm, outPadding, opts); - } - } catch (IOException ex) { - // invalid filedescriptor, no need to call nativeDecodeFileDescriptor() - return null; - } - //Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts); - //return finishDecode(bm, outPadding, opts); - */ - } - - /** - * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded - * return null. The position within the descriptor will not be changed when - * this returns, so the descriptor can be used again as is. - * - * @param fd The file descriptor containing the bitmap data to decode - * @return the decoded bitmap, or null - */ - public static Bitmap decodeFileDescriptor(FileDescriptor fd) { - return decodeFileDescriptor(fd, null, null); - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java new file mode 100644 index 0000000..080b85f --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2011 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.Bridge; +import com.android.layoutlib.bridge.android.BridgeResources.NinePatchInputStream; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.ninepatch.NinePatchChunk; +import com.android.resources.Density; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.BitmapFactory.Options; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; + +/** + * Delegate implementing the native methods of android.graphics.BitmapFactory + * + * Through the layoutlib_create tool, the original native methods of BitmapFactory have been + * replaced by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +/*package*/ class BitmapFactory_Delegate { + + // ------ Java delegates ------ + + @LayoutlibDelegate + /*package*/ static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { + if (bm == null || opts == null) { + return bm; + } + + final int density = opts.inDensity; + if (density == 0) { + return bm; + } + + bm.setDensity(density); + final int targetDensity = opts.inTargetDensity; + if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) { + return bm; + } + + byte[] np = bm.getNinePatchChunk(); + final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); + // DELEGATE CHANGE: never scale 9-patch + if (opts.inScaled && isNinePatch == false) { + float scale = targetDensity / (float)density; + // TODO: This is very inefficient and should be done in native by Skia + final Bitmap oldBitmap = bm; + bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f), + (int) (bm.getHeight() * scale + 0.5f), true); + oldBitmap.recycle(); + + if (isNinePatch) { + np = nativeScaleNinePatch(np, scale, outPadding); + bm.setNinePatchChunk(np); + } + bm.setDensity(targetDensity); + } + + return bm; + } + + + // ------ Native Delegates ------ + + @LayoutlibDelegate + /*package*/ static void nativeSetDefaultConfig(int nativeConfig) { + // pass + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage, + Rect padding, Options opts) { + Bitmap bm = null; + + Density density = Density.MEDIUM; + if (opts != null) { + density = Density.getEnum(opts.inDensity); + } + + try { + if (is instanceof NinePatchInputStream) { + NinePatchInputStream npis = (NinePatchInputStream) is; + npis.disableFakeMarkSupport(); + + // load the bitmap as a nine patch + com.android.ninepatch.NinePatch ninePatch = com.android.ninepatch.NinePatch.load( + npis, true /*is9Patch*/, false /*convert*/); + + // get the bitmap and chunk objects. + bm = Bitmap_Delegate.createBitmap(ninePatch.getImage(), true /*isMutable*/, + density); + NinePatchChunk chunk = ninePatch.getChunk(); + + // put the chunk in the bitmap + bm.setNinePatchChunk(NinePatch_Delegate.serialize(chunk)); + + // read the padding + int[] paddingarray = chunk.getPadding(); + padding.left = paddingarray[0]; + padding.top = paddingarray[1]; + padding.right = paddingarray[2]; + padding.bottom = paddingarray[3]; + } else { + // load the bitmap directly. + bm = Bitmap_Delegate.createBitmap(is, true, density); + } + } catch (IOException e) { + Bridge.getLog().error(null,"Failed to load image" , e, null); + } + + return bm; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, + Rect padding, Options opts) { + opts.inBitmap = null; + return null; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts) { + opts.inBitmap = null; + return null; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset, + int length, Options opts) { + opts.inBitmap = null; + return null; + } + + @LayoutlibDelegate + /*package*/ static byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad) { + // don't scale for now. This should not be called anyway since we re-implement + // BitmapFactory.finishDecode(); + return chunk; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeIsSeekable(FileDescriptor fd) { + return true; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java deleted file mode 100644 index ad3974c..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java +++ /dev/null @@ -1,48 +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.Paint; - -public class BitmapShader extends Shader { - - // we hold on just for the GC, since our native counterpart is using it - private final Bitmap mBitmap; - - /** - * Call this to create a new shader that will draw with a bitmap. - * - * @param bitmap The bitmap to use inside the shader - * @param tileX The tiling mode for x to draw the bitmap in. - * @param tileY The tiling mode for y to draw the bitmap in. - */ - public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) { - mBitmap = bitmap; - } - - //---------- Custom methods - - public Bitmap getBitmap() { - return mBitmap; - } - - @Override - Paint getJavaPaint() { - return null; - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java new file mode 100644 index 0000000..9a8cf04 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java @@ -0,0 +1,247 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.BitmapShader + * + * Through the layoutlib_create tool, the original native methods of BitmapShader 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 BitmapShader class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class BitmapShader_Delegate extends Shader_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public String getSupportMessage() { + // no message since isSupported returns true; + return null; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int native_bitmap, int shaderTileModeX, + int shaderTileModeY) { + Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(native_bitmap); + if (bitmap == null) { + return 0; + } + + BitmapShader_Delegate newDelegate = new BitmapShader_Delegate( + bitmap.getImage(), + Shader_Delegate.getTileMode(shaderTileModeX), + Shader_Delegate.getTileMode(shaderTileModeY)); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate(int native_shader, int native_bitmap, + int shaderTileModeX, int shaderTileModeY) { + // pass, not needed. + return 0; + } + + // ---- Private delegate/helper methods ---- + + private BitmapShader_Delegate(java.awt.image.BufferedImage image, + TileMode tileModeX, TileMode tileModeY) { + mJavaPaint = new BitmapShaderPaint(image, tileModeX, tileModeY); + } + + private class BitmapShaderPaint implements java.awt.Paint { + private final java.awt.image.BufferedImage mImage; + private final TileMode mTileModeX; + private final TileMode mTileModeY; + + BitmapShaderPaint(java.awt.image.BufferedImage image, + TileMode tileModeX, TileMode tileModeY) { + mImage = image; + mTileModeX = tileModeX; + mTileModeY = tileModeY; + } + + public java.awt.PaintContext createContext( + java.awt.image.ColorModel colorModel, + java.awt.Rectangle deviceBounds, + java.awt.geom.Rectangle2D userBounds, + java.awt.geom.AffineTransform xform, + java.awt.RenderingHints hints) { + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in BitmapShader", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in BitmapShader", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new BitmapShaderContext(canvasMatrix, localMatrix, colorModel); + } + + private class BitmapShaderContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + public BitmapShaderContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + mColorModel = colorModel; + } + + public void dispose() { + } + + public java.awt.image.ColorModel getColorModel() { + return mColorModel; + } + + public java.awt.image.Raster getRaster(int x, int y, int w, int h) { + java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, + java.awt.image.BufferedImage.TYPE_INT_ARGB); + + int[] data = new int[w*h]; + + int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix. + pt1[0] = pt2[0]; + pt1[1] = pt2[1]; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + data[index++] = getColor(pt2[0], pt2[1]); + } + } + + image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); + + return image.getRaster(); + } + } + + /** + * Returns a color for an arbitrary point. + */ + private int getColor(float fx, float fy) { + int x = getCoordinate(Math.round(fx), mImage.getWidth(), mTileModeX); + int y = getCoordinate(Math.round(fy), mImage.getHeight(), mTileModeY); + + return mImage.getRGB(x, y); + } + + private int getCoordinate(int i, int size, TileMode mode) { + if (i < 0) { + switch (mode) { + case CLAMP: + i = 0; + break; + case REPEAT: + i = size - 1 - (-i % size); + break; + case MIRROR: + // this is the same as the positive side, just make the value positive + // first. + i = -i; + int count = i / size; + i = i % size; + + if ((count % 2) == 1) { + i = size - 1 - i; + } + break; + } + } else if (i >= size) { + switch (mode) { + case CLAMP: + i = size - 1; + break; + case REPEAT: + i = i % size; + break; + case MIRROR: + int count = i / size; + i = i % size; + + if ((count % 2) == 1) { + i = size - 1 - i; + } + break; + } + } + + return i; + } + + + public int getTransparency() { + return java.awt.Paint.TRANSLUCENT; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java new file mode 100644 index 0000000..66e59d8 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -0,0 +1,563 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.resources.Density; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.os.Parcel; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.Buffer; +import java.util.Arrays; + +import javax.imageio.ImageIO; + +/** + * Delegate implementing the native methods of android.graphics.Bitmap + * + * Through the layoutlib_create tool, the original native methods of Bitmap 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 Bitmap class. + * + * @see DelegateManager + * + */ +public final class Bitmap_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Bitmap_Delegate> sManager = + new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + private final Config mConfig; + private BufferedImage mImage; + private boolean mHasAlpha = true; + private int mGenerationId = 0; + + + // ---- Public Helper methods ---- + + /** + * Returns the native delegate associated to a given {@link Bitmap_Delegate} object. + */ + public static Bitmap_Delegate getDelegate(Bitmap bitmap) { + return sManager.getDelegate(bitmap.mNativeBitmap); + } + + /** + * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object. + */ + public static Bitmap_Delegate getDelegate(int native_bitmap) { + return sManager.getDelegate(native_bitmap); + } + + /** + * Creates and returns a {@link Bitmap} initialized with the given file content. + * + * @param input the file from which to read the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() + */ + public static Bitmap createBitmap(File input, boolean isMutable, Density density) + throws IOException { + // create a delegate with the content of the file. + Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888); + + return createBitmap(delegate, isMutable, density.getDpiValue()); + } + + /** + * Creates and returns a {@link Bitmap} initialized with the given stream content. + * + * @param input the stream from which to read the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() + */ + public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density) + throws IOException { + // create a delegate with the content of the stream. + Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888); + + return createBitmap(delegate, isMutable, density.getDpiValue()); + } + + /** + * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage} + * + * @param image the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() + */ + public static Bitmap createBitmap(BufferedImage image, boolean isMutable, + Density density) throws IOException { + // create a delegate with the given image. + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888); + + return createBitmap(delegate, isMutable, density.getDpiValue()); + } + + /** + * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}. + */ + public static BufferedImage getImage(Bitmap bitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(bitmap.mNativeBitmap); + if (delegate == null) { + return null; + } + + return delegate.mImage; + } + + public static int getBufferedImageType(int nativeBitmapConfig) { + switch (Config.sConfigs[nativeBitmapConfig]) { + case ALPHA_8: + return BufferedImage.TYPE_INT_ARGB; + case RGB_565: + return BufferedImage.TYPE_INT_ARGB; + case ARGB_4444: + return BufferedImage.TYPE_INT_ARGB; + case ARGB_8888: + return BufferedImage.TYPE_INT_ARGB; + } + + return BufferedImage.TYPE_INT_ARGB; + } + + /** + * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}. + */ + public BufferedImage getImage() { + return mImage; + } + + /** + * Returns the Android bitmap config. Note that this not the config of the underlying + * Java2D bitmap. + */ + public Config getConfig() { + return mConfig; + } + + /** + * Returns the hasAlpha rendering hint + * @return true if the bitmap alpha should be used at render time + */ + public boolean hasAlpha() { + return mHasAlpha && mConfig != Config.RGB_565; + } + + /** + * Update the generationId. + * + * @see Bitmap#getGenerationId() + */ + public void change() { + mGenerationId++; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width, + int height, int nativeConfig, boolean mutable) { + int imageType = getBufferedImageType(nativeConfig); + + // create the image + BufferedImage image = new BufferedImage(width, height, imageType); + + if (colors != null) { + image.setRGB(0, 0, width, height, colors, offset, stride); + } + + // create a delegate with the content of the stream. + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]); + + return createBitmap(delegate, mutable, Bitmap.getDefaultDensity()); + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) { + Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap); + if (srcBmpDelegate == null) { + return null; + } + + BufferedImage srcImage = srcBmpDelegate.getImage(); + + int width = srcImage.getWidth(); + int height = srcImage.getHeight(); + + int imageType = getBufferedImageType(nativeConfig); + + // create the image + BufferedImage image = new BufferedImage(width, height, imageType); + + // copy the source image into the image. + int[] argb = new int[width * height]; + srcImage.getRGB(0, 0, width, height, argb, 0, width); + image.setRGB(0, 0, width, height, argb, 0, width); + + // create a delegate with the content of the stream. + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]); + + return createBitmap(delegate, isMutable, Bitmap.getDefaultDensity()); + } + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int nativeBitmap) { + sManager.removeJavaReferenceFor(nativeBitmap); + } + + @LayoutlibDelegate + /*package*/ static void nativeRecycle(int nativeBitmap) { + sManager.removeJavaReferenceFor(nativeBitmap); + } + + @LayoutlibDelegate + /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality, + OutputStream stream, byte[] tempStorage) { + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "Bitmap.compress() is not supported", null /*data*/); + return true; + } + + @LayoutlibDelegate + /*package*/ static void nativeErase(int nativeBitmap, int color) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + BufferedImage image = delegate.mImage; + + Graphics2D g = image.createGraphics(); + try { + g.setColor(new java.awt.Color(color, true)); + + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + } finally { + g.dispose(); + } + } + + @LayoutlibDelegate + /*package*/ static int nativeWidth(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getWidth(); + } + + @LayoutlibDelegate + /*package*/ static int nativeHeight(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getHeight(); + } + + @LayoutlibDelegate + /*package*/ static int nativeRowBytes(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getWidth(); + } + + @LayoutlibDelegate + /*package*/ static int nativeConfig(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mConfig.nativeInt; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeHasAlpha(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return true; + } + + return delegate.mHasAlpha; + } + + @LayoutlibDelegate + /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getRGB(x, y); + } + + @LayoutlibDelegate + /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset, + int stride, int x, int y, int width, int height) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride); + } + + + @LayoutlibDelegate + /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.getImage().setRGB(x, y, color); + } + + @LayoutlibDelegate + /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset, + int stride, int x, int y, int width, int height) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.getImage().setRGB(x, y, width, height, colors, offset, stride); + } + + @LayoutlibDelegate + /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) { + // FIXME implement native delegate + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) { + // FIXME implement native delegate + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static int nativeGenerationId(int nativeBitmap) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mGenerationId; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) { + // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only + // used during aidl call so really this should not be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.", + null /*data*/); + return null; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable, + int density, Parcel p) { + // This is only called when sending a bitmap through aidl, so really this should not + // be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.", + null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint, + int[] offsetXY) { + Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap); + if (bitmap == null) { + return null; + } + + // get the paint which can be null if nativePaint is 0. + Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint); + + if (paint != null && paint.getMaskFilter() != null) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, + "MaskFilter not supported in Bitmap.extractAlpha", + null, null /*data*/); + } + + int alpha = paint != null ? paint.getAlpha() : 0xFF; + BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha); + + // create the delegate. The actual Bitmap config is only an alpha channel + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8); + + // the density doesn't matter, it's set by the Java method. + return createBitmap(delegate, false /*isMutable*/, + Density.DEFAULT_DENSITY /*density*/); + } + + @LayoutlibDelegate + /*package*/ static void nativePrepareToDraw(int nativeBitmap) { + // nothing to be done here. + } + + @LayoutlibDelegate + /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.mHasAlpha = hasAlpha; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSameAs(int nb0, int nb1) { + Bitmap_Delegate delegate1 = sManager.getDelegate(nb0); + if (delegate1 == null) { + return false; + } + + Bitmap_Delegate delegate2 = sManager.getDelegate(nb1); + if (delegate2 == null) { + return false; + } + + BufferedImage image1 = delegate1.getImage(); + BufferedImage image2 = delegate2.getImage(); + if (delegate1.mConfig != delegate2.mConfig || + image1.getWidth() != image2.getWidth() || + image1.getHeight() != image2.getHeight()) { + return false; + } + + // get the internal data + int w = image1.getWidth(); + int h = image2.getHeight(); + int[] argb1 = new int[w*h]; + int[] argb2 = new int[w*h]; + + image1.getRGB(0, 0, w, h, argb1, 0, w); + image2.getRGB(0, 0, w, h, argb2, 0, w); + + // compares + if (delegate1.mConfig == Config.ALPHA_8) { + // in this case we have to manually compare the alpha channel as the rest is garbage. + final int length = w*h; + for (int i = 0 ; i < length ; i++) { + if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) { + return false; + } + } + return true; + } + + return Arrays.equals(argb1, argb2); + } + + // ---- Private delegate/helper methods ---- + + private Bitmap_Delegate(BufferedImage image, Config config) { + mImage = image; + mConfig = config; + } + + private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) { + // get its native_int + int nativeInt = sManager.addNewDelegate(delegate); + + // and create/return a new Bitmap with it + return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, density); + } + + /** + * Creates and returns a copy of a given BufferedImage. + * <p/> + * if alpha is different than 255, then it is applied to the alpha channel of each pixel. + * + * @param image the image to copy + * @param imageType the type of the new image + * @param alpha an optional alpha modifier + * @return a new BufferedImage + */ + /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) { + int w = image.getWidth(); + int h = image.getHeight(); + + BufferedImage result = new BufferedImage(w, h, imageType); + + int[] argb = new int[w * h]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); + + if (alpha != 255) { + final int length = argb.length; + for (int i = 0 ; i < length; i++) { + int a = (argb[i] >>> 24 * alpha) / 255; + argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF); + } + } + + result.setRGB(0, 0, w, h, argb, 0, w); + + return result; + } + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java new file mode 100644 index 0000000..4becba1 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.BlurMaskFilter + * + * Through the layoutlib_create tool, the original native methods of BlurMaskFilter 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 BlurMaskFilter class. + * + * Because this extends {@link MaskFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link MaskFilter_Delegate}. + * + * @see MaskFilter_Delegate + * + */ +public class BlurMaskFilter_Delegate extends MaskFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Blur Mask Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor(float radius, int style) { + BlurMaskFilter_Delegate newDelegate = new BlurMaskFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java deleted file mode 100644 index d5d315e..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java +++ /dev/null @@ -1,1279 +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 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; -import android.graphics.Region.Op; - -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.geom.AffineTransform; -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. - */ -public class Canvas extends _Original_Canvas { - - private BufferedImage mBufferedImage; - private final Stack<Graphics2D> mGraphicsStack = new Stack<Graphics2D>(); - private final ILayoutLog mLogger; - - public Canvas() { - mLogger = null; - // the mBufferedImage will be taken from a bitmap in #setBitmap() - } - - public Canvas(Bitmap bitmap) { - mLogger = null; - mBufferedImage = bitmap.getImage(); - mGraphicsStack.push(mBufferedImage.createGraphics()); - } - - public Canvas(int nativeCanvas) { - mLogger = null; - 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; - mBufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - mGraphicsStack.push(mBufferedImage.createGraphics()); - } - - public Canvas(int width, int height) { - this(width, height, null /* logger*/); - } - - // custom mehtods - public BufferedImage getImage() { - return mBufferedImage; - } - - public Graphics2D getGraphics2d() { - return mGraphicsStack.peek(); - } - - public void dispose() { - while (mGraphicsStack.size() > 0) { - mGraphicsStack.pop().dispose(); - } - } - - /** - * Creates a new {@link Graphics2D} based on the {@link Paint} parameters. - * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. - */ - private Graphics2D getCustomGraphics(Paint paint) { - // make new one - Graphics2D g = getGraphics2d(); - g = (Graphics2D)g.create(); - - // configure it - g.setColor(new Color(paint.getColor())); - int alpha = paint.getAlpha(); - float falpha = alpha / 255.f; - - Style style = paint.getStyle(); - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - PathEffect e = paint.getPathEffect(); - if (e instanceof DashPathEffect) { - DashPathEffect dpe = (DashPathEffect)e; - g.setStroke(new BasicStroke( - paint.getStrokeWidth(), - paint.getStrokeCap().getJavaCap(), - paint.getStrokeJoin().getJavaJoin(), - paint.getStrokeMiter(), - dpe.getIntervals(), - dpe.getPhase())); - } else { - g.setStroke(new BasicStroke( - paint.getStrokeWidth(), - paint.getStrokeCap().getJavaCap(), - paint.getStrokeJoin().getJavaJoin(), - paint.getStrokeMiter())); - } - } - - Xfermode xfermode = paint.getXfermode(); - if (xfermode instanceof PorterDuffXfermode) { - PorterDuff.Mode mode = ((PorterDuffXfermode)xfermode).getMode(); - - setModeInGraphics(mode, g, falpha); - } else { - if (mLogger != null && xfermode != null) { - mLogger.warning(String.format( - "Xfermode '%1$s' is not supported in the Layout Editor.", - xfermode.getClass().getCanonicalName())); - } - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); - } - - Shader shader = paint.getShader(); - if (shader != null) { - java.awt.Paint shaderPaint = shader.getJavaPaint(); - if (shaderPaint != null) { - g.setPaint(shaderPaint); - } else { - if (mLogger != null) { - mLogger.warning(String.format( - "Shader '%1$s' is not supported in the Layout Editor.", - shader.getClass().getCanonicalName())); - } - } - } - - return g; - } - - private void setModeInGraphics(PorterDuff.Mode mode, Graphics2D g, float falpha) { - switch (mode) { - case CLEAR: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, falpha)); - break; - case DARKEN: - break; - case DST: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST, falpha)); - break; - case DST_ATOP: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha)); - break; - case DST_IN: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha)); - break; - case DST_OUT: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha)); - break; - case DST_OVER: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha)); - break; - case LIGHTEN: - break; - case MULTIPLY: - break; - case SCREEN: - break; - case SRC: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, falpha)); - break; - case SRC_ATOP: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha)); - break; - case SRC_IN: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha)); - break; - case SRC_OUT: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha)); - break; - case SRC_OVER: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); - break; - case XOR: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.XOR, falpha)); - break; - } - } - - - // -------------------- - // OVERRIDEN ENUMS - // This is needed since we rename Canvas into _Original_Canvas - // -------------------- - - public enum EdgeType { - BW(0), //!< treat edges by just rounding to nearest pixel boundary - AA(1); //!< treat edges by rounding-out, since they may be antialiased - - EdgeType(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - - // -------------------- - // OVERRIDEN METHODS - // -------------------- - - /* (non-Javadoc) - * @see android.graphics.Canvas#setBitmap(android.graphics.Bitmap) - */ - @Override - public void setBitmap(Bitmap bitmap) { - mBufferedImage = bitmap.getImage(); - mGraphicsStack.push(mBufferedImage.createGraphics()); - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#translate(float, float) - */ - @Override - public void translate(float dx, float dy) { - getGraphics2d().translate(dx, dy); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#save() - */ - @Override - public int save() { - // get the current save count - int count = mGraphicsStack.size(); - - // create a new graphics and add it to the stack - Graphics2D g = (Graphics2D)getGraphics2d().create(); - mGraphicsStack.push(g); - - // return the old save count - return count; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#save(int) - */ - @Override - public int save(int saveFlags) { - // For now we ignore saveFlags - return save(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#restore() - */ - @Override - public void restore() { - mGraphicsStack.pop(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#restoreToCount(int) - */ - @Override - public void restoreToCount(int saveCount) { - while (mGraphicsStack.size() > saveCount) { - mGraphicsStack.pop(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getSaveCount() - */ - @Override - public int getSaveCount() { - return mGraphicsStack.size(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(float, float, float, float, android.graphics.Region.Op) - */ - @Override - public boolean clipRect(float left, float top, float right, float bottom, Op op) { - return clipRect(left, top, right, bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(float, float, float, float) - */ - @Override - public boolean clipRect(float left, float top, float right, float bottom) { - getGraphics2d().clipRect((int)left, (int)top, (int)(right-left), (int)(bottom-top)); - return true; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(int, int, int, int) - */ - @Override - public boolean clipRect(int left, int top, int right, int bottom) { - getGraphics2d().clipRect(left, top, right-left, bottom-top); - return true; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.Rect, android.graphics.Region.Op) - */ - @Override - public boolean clipRect(Rect rect, Op op) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.Rect) - */ - @Override - public boolean clipRect(Rect rect) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.RectF, android.graphics.Region.Op) - */ - @Override - public boolean clipRect(RectF rect, Op op) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.RectF) - */ - @Override - public boolean clipRect(RectF rect) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - public boolean quickReject(RectF rect, EdgeType type) { - return false; - } - - @Override - public boolean quickReject(RectF rect, _Original_Canvas.EdgeType type) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public boolean quickReject(Path path, EdgeType type) { - return false; - } - - @Override - public boolean quickReject(Path path, _Original_Canvas.EdgeType type) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public boolean quickReject(float left, float top, float right, float bottom, - EdgeType type) { - return false; - } - - @Override - public boolean quickReject(float left, float top, float right, float bottom, - _Original_Canvas.EdgeType type) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Retrieve the clip bounds, returning true if they are non-empty. - * - * @param bounds Return the clip bounds here. If it is null, ignore it but - * still return true if the current clip is non-empty. - * @return true if the current clip is non-empty. - */ - @Override - public boolean getClipBounds(Rect bounds) { - Rectangle rect = getGraphics2d().getClipBounds(); - if (rect != null) { - bounds.left = rect.x; - bounds.top = rect.y; - bounds.right = rect.x + rect.width; - bounds.bottom = rect.y + rect.height; - return true; - } - return false; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawColor(int, android.graphics.PorterDuff.Mode) - */ - @Override - public void drawColor(int color, PorterDuff.Mode mode) { - Graphics2D g = getGraphics2d(); - - // save old color - Color c = g.getColor(); - - Composite composite = g.getComposite(); - - // get the alpha from the color - int alpha = color >>> 24; - float falpha = alpha / 255.f; - - setModeInGraphics(mode, g, falpha); - - g.setColor(new Color(color)); - - g.fillRect(0, 0, getWidth(), getHeight()); - - g.setComposite(composite); - - // restore color - g.setColor(c); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawColor(int) - */ - @Override - public void drawColor(int color) { - drawColor(color, PorterDuff.Mode.SRC_OVER); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawARGB(int, int, int, int) - */ - @Override - public void drawARGB(int a, int r, int g, int b) { - drawColor(a << 24 | r << 16 | g << 8 | b, PorterDuff.Mode.SRC_OVER); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRGB(int, int, int) - */ - @Override - public void drawRGB(int r, int g, int b) { - drawColor(0xFF << 24 | r << 16 | g << 8 | b, PorterDuff.Mode.SRC_OVER); - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#getWidth() - */ - @Override - public int getWidth() { - return mBufferedImage.getWidth(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getHeight() - */ - @Override - public int getHeight() { - return mBufferedImage.getHeight(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPaint(android.graphics.Paint) - */ - @Override - public void drawPaint(Paint paint) { - drawColor(paint.getColor()); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, float, float, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { - drawBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), - (int)left, (int)top, - (int)left+bitmap.getWidth(), (int)top+bitmap.getHeight(), paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, android.graphics.Matrix, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { - boolean needsRestore = false; - if (matrix.isIdentity() == false) { - // create a new graphics and apply the matrix to it - save(); // this creates a new Graphics2D, and stores it for children call to use - needsRestore = true; - Graphics2D g = getGraphics2d(); // get the newly create Graphics2D - - // get the Graphics2D current matrix - AffineTransform currentTx = g.getTransform(); - // get the AffineTransform from the matrix - AffineTransform matrixTx = matrix.getTransform(); - - // combine them so that the matrix is applied after. - currentTx.preConcatenate(matrixTx); - - // give it to the graphics as a new matrix replacing all previous transform - g.setTransform(currentTx); - } - - // draw the bitmap - drawBitmap(bitmap, 0, 0, paint); - - if (needsRestore) { - // remove the new graphics - restore(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, android.graphics.Rect, android.graphics.Rect, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { - if (src == null) { - drawBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), - dst.left, dst.top, dst.right, dst.bottom, paint); - } else { - drawBitmap(bitmap, src.left, src.top, src.width(), src.height(), - dst.left, dst.top, dst.right, dst.bottom, paint); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, android.graphics.Rect, android.graphics.RectF, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { - if (src == null) { - drawBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), - (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom, paint); - } else { - drawBitmap(bitmap, src.left, src.top, src.width(), src.height(), - (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom, paint); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(int[], int, int, int, int, int, int, boolean, android.graphics.Paint) - */ - @Override - public void drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, - int height, boolean hasAlpha, Paint paint) { - throw new UnsupportedOperationException(); - } - - private void drawBitmap(Bitmap bitmap, int sleft, int stop, int sright, int sbottom, int dleft, - int dtop, int dright, int dbottom, Paint paint) { - BufferedImage image = bitmap.getImage(); - - Graphics2D g = getGraphics2d(); - - Composite c = null; - - if (paint != null) { - if (paint.isFilterBitmap()) { - g = (Graphics2D)g.create(); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - } - - if (paint.getAlpha() != 0xFF) { - c = g.getComposite(); - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - paint.getAlpha()/255.f)); - } - } - - g.drawImage(image, dleft, dtop, dright, dbottom, - sleft, stop, sright, sbottom, null); - - if (paint != null) { - if (paint.isFilterBitmap()) { - g.dispose(); - } - if (c != null) { - g.setComposite(c); - } - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#rotate(float, float, float) - */ - @Override - public void rotate(float degrees, float px, float py) { - if (degrees != 0) { - Graphics2D g = getGraphics2d(); - g.translate(px, py); - g.rotate(Math.toRadians(degrees)); - g.translate(-px, -py); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#rotate(float) - */ - @Override - public void rotate(float degrees) { - getGraphics2d().rotate(Math.toRadians(degrees)); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#scale(float, float, float, float) - */ - @Override - public void scale(float sx, float sy, float px, float py) { - Graphics2D g = getGraphics2d(); - g.translate(px, py); - g.scale(sx, sy); - g.translate(-px, -py); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#scale(float, float) - */ - @Override - public void scale(float sx, float sy) { - getGraphics2d().scale(sx, sy); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(char[], int, int, float, float, android.graphics.Paint) - */ - @Override - public void drawText(char[] text, int index, int count, float x, float y, Paint paint) { - // WARNING: the logic in this method is similar to Paint.measureText. - // Any change to this method should be reflected in Paint.measureText - Graphics2D g = getGraphics2d(); - - g = (Graphics2D)g.create(); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // set the color. because this only handles RGB, the alpha channel is handled - // as a composite. - g.setColor(new Color(paint.getColor())); - int alpha = paint.getAlpha(); - float falpha = alpha / 255.f; - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); - - - // Paint.TextAlign indicates how the text is positioned relative to X. - // LEFT is the default and there's nothing to do. - if (paint.getTextAlign() != Align.LEFT) { - float m = paint.measureText(text, index, count); - if (paint.getTextAlign() == Align.CENTER) { - x -= m / 2; - } else if (paint.getTextAlign() == Align.RIGHT) { - x -= m; - } - } - - List<FontInfo> fonts = paint.getFonts(); - try { - if (fonts.size() > 0) { - FontInfo mainFont = fonts.get(0); - int i = index; - int lastIndex = index + count; - while (i < lastIndex) { - // always start with the main font. - int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); - if (upTo == -1) { - // draw all the rest and exit. - g.setFont(mainFont.mFont); - g.drawChars(text, i, lastIndex - i, (int)x, (int)y); - return; - } else if (upTo > 0) { - // draw what's possible - g.setFont(mainFont.mFont); - g.drawChars(text, i, upTo - i, (int)x, (int)y); - - // compute the width that was drawn to increase x - x += mainFont.mMetrics.charsWidth(text, i, upTo - i); - - // move index to the first non displayed char. - i = upTo; - - // don't call continue at this point. Since it is certain the main font - // cannot display the font a index upTo (now ==i), we move on to the - // fallback fonts directly. - } - - // no char supported, attempt to read the next char(s) with the - // fallback font. In this case we only test the first character - // and then go back to test with the main font. - // Special test for 2-char characters. - boolean foundFont = false; - for (int f = 1 ; f < fonts.size() ; f++) { - FontInfo fontInfo = fonts.get(f); - - // need to check that the font can display the character. We test - // differently if the char is a high surrogate. - int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; - upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); - if (upTo == -1) { - // draw that char - g.setFont(fontInfo.mFont); - g.drawChars(text, i, charCount, (int)x, (int)y); - - // update x - x += fontInfo.mMetrics.charsWidth(text, i, charCount); - - // update the index in the text, and move on - i += charCount; - foundFont = true; - break; - - } - } - - // in case no font can display the char, display it with the main font. - // (it'll put a square probably) - if (foundFont == false) { - int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; - - g.setFont(mainFont.mFont); - g.drawChars(text, i, charCount, (int)x, (int)y); - - // measure it to advance x - x += mainFont.mMetrics.charsWidth(text, i, charCount); - - // and move to the next chars. - i += charCount; - } - } - } - } finally { - g.dispose(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) - */ - @Override - public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { - drawText(text.toString().toCharArray(), start, end - start, x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(java.lang.String, float, float, android.graphics.Paint) - */ - @Override - public void drawText(String text, float x, float y, Paint paint) { - drawText(text.toCharArray(), 0, text.length(), x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(java.lang.String, int, int, float, float, android.graphics.Paint) - */ - @Override - public void drawText(String text, int start, int end, float x, float y, Paint paint) { - drawText(text.toCharArray(), start, end - start, x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRect(android.graphics.RectF, android.graphics.Paint) - */ - @Override - public void drawRect(RectF rect, Paint paint) { - doDrawRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(), paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRect(float, float, float, float, android.graphics.Paint) - */ - @Override - public void drawRect(float left, float top, float right, float bottom, Paint paint) { - doDrawRect((int)left, (int)top, (int)(right-left), (int)(bottom-top), paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRect(android.graphics.Rect, android.graphics.Paint) - */ - @Override - public void drawRect(Rect r, Paint paint) { - doDrawRect(r.left, r.top, r.width(), r.height(), paint); - } - - private final void doDrawRect(int left, int top, int width, int height, Paint paint) { - if (width > 0 && height > 0) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillRect(left, top, width, height); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawRect(left, top, width, height); - } - - // dispose Graphics2D object - g.dispose(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRoundRect(android.graphics.RectF, float, float, android.graphics.Paint) - */ - @Override - public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { - if (rect.width() > 0 && rect.height() > 0) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - - int arcWidth = (int)(rx * 2); - int arcHeight = (int)(ry * 2); - - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(), - arcWidth, arcHeight); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(), - arcWidth, arcHeight); - } - - // dispose Graphics2D object - g.dispose(); - } - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawLine(float, float, float, float, android.graphics.Paint) - */ - @Override - public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - g.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY); - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawLines(float[], int, int, android.graphics.Paint) - */ - @Override - public void drawLines(float[] pts, int offset, int count, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - for (int i = 0 ; i < count ; i += 4) { - g.drawLine((int)pts[i + offset], (int)pts[i + offset + 1], - (int)pts[i + offset + 2], (int)pts[i + offset + 3]); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawLines(float[], android.graphics.Paint) - */ - @Override - public void drawLines(float[] pts, Paint paint) { - drawLines(pts, 0, pts.length, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawCircle(float, float, float, android.graphics.Paint) - */ - @Override - public void drawCircle(float cx, float cy, float radius, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - int size = (int)(radius * 2); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillOval((int)(cx - radius), (int)(cy - radius), size, size); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawOval((int)(cx - radius), (int)(cy - radius), size, size); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawOval(android.graphics.RectF, android.graphics.Paint) - */ - @Override - public void drawOval(RectF oval, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height()); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height()); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPath(android.graphics.Path, android.graphics.Paint) - */ - @Override - public void drawPath(Path path, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fill(path.getAwtShape()); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.draw(path.getAwtShape()); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#setMatrix(android.graphics.Matrix) - */ - @Override - public void setMatrix(Matrix matrix) { - // get the new current graphics - Graphics2D g = getGraphics2d(); - - // and apply the matrix - g.setTransform(matrix.getTransform()); - - if (mLogger != null && matrix.hasPerspective()) { - mLogger.warning("android.graphics.Canvas#setMatrix(android.graphics.Matrix) only supports affine transformations in the Layout Editor."); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#concat(android.graphics.Matrix) - */ - @Override - public void concat(Matrix matrix) { - // get the current top graphics2D object. - Graphics2D g = getGraphics2d(); - - // get its current matrix - AffineTransform currentTx = g.getTransform(); - // get the AffineTransform of the given matrix - AffineTransform matrixTx = matrix.getTransform(); - - // combine them so that the given matrix is applied after. - currentTx.preConcatenate(matrixTx); - - // give it to the graphics2D as a new matrix replacing all previous transform - g.setTransform(currentTx); - } - - - // -------------------- - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipPath(android.graphics.Path, android.graphics.Region.Op) - */ - @Override - public boolean clipPath(Path path, Op op) { - // TODO Auto-generated method stub - return super.clipPath(path, op); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipPath(android.graphics.Path) - */ - @Override - public boolean clipPath(Path path) { - // TODO Auto-generated method stub - return super.clipPath(path); - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRegion(android.graphics.Region, android.graphics.Region.Op) - */ - @Override - public boolean clipRegion(Region region, Op op) { - // TODO Auto-generated method stub - return super.clipRegion(region, op); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRegion(android.graphics.Region) - */ - @Override - public boolean clipRegion(Region region) { - // TODO Auto-generated method stub - return super.clipRegion(region); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawArc(android.graphics.RectF, float, float, boolean, android.graphics.Paint) - */ - @Override - public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, - Paint paint) { - // TODO Auto-generated method stub - super.drawArc(oval, startAngle, sweepAngle, useCenter, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmapMesh(android.graphics.Bitmap, int, int, float[], int, int[], int, android.graphics.Paint) - */ - @Override - public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, - int vertOffset, int[] colors, int colorOffset, Paint paint) { - // TODO Auto-generated method stub - super.drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPicture(android.graphics.Picture, android.graphics.Rect) - */ - @Override - public void drawPicture(Picture picture, Rect dst) { - // TODO Auto-generated method stub - super.drawPicture(picture, dst); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPicture(android.graphics.Picture, android.graphics.RectF) - */ - @Override - public void drawPicture(Picture picture, RectF dst) { - // TODO Auto-generated method stub - super.drawPicture(picture, dst); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPicture(android.graphics.Picture) - */ - @Override - public void drawPicture(Picture picture) { - // TODO Auto-generated method stub - super.drawPicture(picture); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPoint(float, float, android.graphics.Paint) - */ - @Override - public void drawPoint(float x, float y, Paint paint) { - // TODO Auto-generated method stub - super.drawPoint(x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPoints(float[], int, int, android.graphics.Paint) - */ - @Override - public void drawPoints(float[] pts, int offset, int count, Paint paint) { - // TODO Auto-generated method stub - super.drawPoints(pts, offset, count, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPoints(float[], android.graphics.Paint) - */ - @Override - public void drawPoints(float[] pts, Paint paint) { - // TODO Auto-generated method stub - super.drawPoints(pts, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPosText(char[], int, int, float[], android.graphics.Paint) - */ - @Override - public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { - // TODO Auto-generated method stub - super.drawPosText(text, index, count, pos, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPosText(java.lang.String, float[], android.graphics.Paint) - */ - @Override - public void drawPosText(String text, float[] pos, Paint paint) { - // TODO Auto-generated method stub - super.drawPosText(text, pos, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawTextOnPath(char[], int, int, android.graphics.Path, float, float, android.graphics.Paint) - */ - @Override - public void drawTextOnPath(char[] text, int index, int count, Path path, float offset, - float offset2, Paint paint) { - // TODO Auto-generated method stub - super.drawTextOnPath(text, index, count, path, offset, offset2, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawTextOnPath(java.lang.String, android.graphics.Path, float, float, android.graphics.Paint) - */ - @Override - public void drawTextOnPath(String text, Path path, float offset, float offset2, Paint paint) { - // TODO Auto-generated method stub - super.drawTextOnPath(text, path, offset, offset2, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawVertices(android.graphics.Canvas.VertexMode, int, float[], int, float[], int, int[], int, short[], int, int, android.graphics.Paint) - */ - @Override - public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, - float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, - int indexOffset, int indexCount, Paint paint) { - // TODO Auto-generated method stub - super.drawVertices(mode, vertexCount, verts, vertOffset, texs, texOffset, colors, colorOffset, - indices, indexOffset, indexCount, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getDrawFilter() - */ - @Override - public DrawFilter getDrawFilter() { - // TODO Auto-generated method stub - return super.getDrawFilter(); - } - - /* (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 - public Matrix getMatrix() { - // TODO Auto-generated method stub - return super.getMatrix(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getMatrix(android.graphics.Matrix) - */ - @Override - public void getMatrix(Matrix ctm) { - // TODO Auto-generated method stub - super.getMatrix(ctm); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#isOpaque() - */ - @Override - public boolean isOpaque() { - // TODO Auto-generated method stub - return super.isOpaque(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayer(float, float, float, float, android.graphics.Paint, int) - */ - @Override - public int saveLayer(float left, float top, float right, float bottom, Paint paint, - int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayer(left, top, right, bottom, paint, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayer(android.graphics.RectF, android.graphics.Paint, int) - */ - @Override - public int saveLayer(RectF bounds, Paint paint, int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayer(bounds, paint, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayerAlpha(float, float, float, float, int, int) - */ - @Override - public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, - int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayerAlpha(left, top, right, bottom, alpha, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayerAlpha(android.graphics.RectF, int, int) - */ - @Override - public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayerAlpha(bounds, alpha, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#setDrawFilter(android.graphics.DrawFilter) - */ - @Override - public void setDrawFilter(DrawFilter filter) { - // TODO Auto-generated method stub - super.setDrawFilter(filter); - } - - /* (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 - public void skew(float sx, float sy) { - // TODO Auto-generated method stub - super.skew(sx, sy); - } - - - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java new file mode 100644 index 0000000..4decd1a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -0,0 +1,1334 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.GcSnapshot; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Bitmap.Config; +import android.graphics.Paint_Delegate.FontInfo; +import android.text.TextUtils; + +import java.awt.Color; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.util.List; + + +/** + * Delegate implementing the native methods of android.graphics.Canvas + * + * Through the layoutlib_create tool, the original native methods of Canvas 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 Canvas class. + * + * @see DelegateManager + * + */ +public final class Canvas_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Canvas_Delegate> sManager = + new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class); + + // ---- delegate helper data ---- + + private final static boolean[] sBoolOut = new boolean[1]; + + // ---- delegate data ---- + private Bitmap_Delegate mBitmap; + private GcSnapshot mSnapshot; + + private DrawFilter_Delegate mDrawFilter = null; + + // ---- Public Helper methods ---- + + /** + * Returns the native delegate associated to a given {@link Canvas} object. + */ + public static Canvas_Delegate getDelegate(Canvas canvas) { + return sManager.getDelegate(canvas.mNativeCanvas); + } + + /** + * Returns the native delegate associated to a given an int referencing a {@link Canvas} object. + */ + public static Canvas_Delegate getDelegate(int native_canvas) { + return sManager.getDelegate(native_canvas); + } + + /** + * Returns the current {@link Graphics2D} used to draw. + */ + public GcSnapshot getSnapshot() { + return mSnapshot; + } + + /** + * Returns the {@link DrawFilter} delegate or null if none have been set. + * + * @return the delegate or null. + */ + public DrawFilter_Delegate getDrawFilter() { + return mDrawFilter; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static boolean isOpaque(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return false; + } + + return canvasDelegate.mBitmap.getConfig() == Config.RGB_565; + } + + @LayoutlibDelegate + /*package*/ static int getWidth(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.mBitmap.getImage().getWidth(); + } + + @LayoutlibDelegate + /*package*/ static int getHeight(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.mBitmap.getImage().getHeight(); + } + + @LayoutlibDelegate + /*package*/ static void translate(Canvas thisCanvas, float dx, float dy) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().translate(dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void rotate(Canvas thisCanvas, float degrees) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees)); + } + + @LayoutlibDelegate + /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().scale(sx, sy); + } + + @LayoutlibDelegate + /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the current top graphics2D object. + GcSnapshot g = canvasDelegate.getSnapshot(); + + // get its current matrix + AffineTransform currentTx = g.getTransform(); + // get the AffineTransform for the given skew. + float[] mtx = Matrix_Delegate.getSkew(kx, ky); + AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx); + + // combine them so that the given matrix is applied after. + currentTx.preConcatenate(matrixTx); + + // give it to the graphics2D as a new matrix replacing all previous transform + g.setTransform(currentTx); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, RectF rect) { + return clipRect(thisCanvas, rect.left, rect.top, rect.right, rect.bottom); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, Rect rect) { + return clipRect(thisCanvas, (float) rect.left, (float) rect.top, + (float) rect.right, (float) rect.bottom); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, float left, float top, float right, + float bottom) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return false; + } + + return canvasDelegate.clipRect(left, top, right, bottom, Region.Op.INTERSECT.nativeInt); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, int left, int top, int right, + int bottom) { + + return clipRect(thisCanvas, (float) left, (float) top, (float) right, (float) bottom); + } + + @LayoutlibDelegate + /*package*/ static int save(Canvas thisCanvas) { + return save(thisCanvas, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG); + } + + @LayoutlibDelegate + /*package*/ static int save(Canvas thisCanvas, int saveFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.save(saveFlags); + } + + @LayoutlibDelegate + /*package*/ static void restore(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.restore(); + } + + @LayoutlibDelegate + /*package*/ static int getSaveCount(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.getSnapshot().size(); + } + + @LayoutlibDelegate + /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.restoreTo(saveCount); + } + + @LayoutlibDelegate + /*package*/ static void drawPoints(Canvas thisCanvas, float[] pts, int offset, int count, + Paint paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void drawPoint(Canvas thisCanvas, float x, float y, Paint paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void drawLines(Canvas thisCanvas, + final float[] pts, final int offset, final int count, + Paint paint) { + draw(thisCanvas.mNativeCanvas, paint.mNativePaint, false /*compositeOnly*/, + false /*forceSrcMode*/, new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + for (int i = 0 ; i < count ; i += 4) { + graphics.drawLine((int)pts[i + offset], (int)pts[i + offset + 1], + (int)pts[i + offset + 2], (int)pts[i + offset + 3]); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void freeCaches() { + // nothing to be done here. + } + + @LayoutlibDelegate + /*package*/ static int initRaster(int nativeBitmapOrZero) { + if (nativeBitmapOrZero > 0) { + // get the Bitmap from the int + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero); + + // create a new Canvas_Delegate with the given bitmap and return its new native int. + Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate); + + return sManager.addNewDelegate(newDelegate); + } else { + // create a new Canvas_Delegate and return its new native int. + Canvas_Delegate newDelegate = new Canvas_Delegate(); + + return sManager.addNewDelegate(newDelegate); + } + } + + @LayoutlibDelegate + /*package*/ static void native_setBitmap(int nativeCanvas, int bitmap) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + canvasDelegate.setBitmap(bitmapDelegate); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayer(int nativeCanvas, RectF bounds, + int paint, int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); + if (paintDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayer(bounds, paintDelegate, layerFlags); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayer(int nativeCanvas, float l, + float t, float r, float b, + int paint, int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); + if (paintDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayer(new RectF(l, t, r, b), + paintDelegate, layerFlags); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayerAlpha(int nativeCanvas, + RectF bounds, int alpha, + int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayerAlpha(bounds, alpha, layerFlags); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayerAlpha(int nativeCanvas, float l, + float t, float r, float b, + int alpha, int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags); + } + + + @LayoutlibDelegate + /*package*/ static void native_concat(int nCanvas, int nMatrix) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + // get the current top graphics2D object. + GcSnapshot snapshot = canvasDelegate.getSnapshot(); + + // get its current matrix + AffineTransform currentTx = snapshot.getTransform(); + // get the AffineTransform of the given matrix + AffineTransform matrixTx = matrixDelegate.getAffineTransform(); + + // combine them so that the given matrix is applied after. + currentTx.preConcatenate(matrixTx); + + // give it to the graphics2D as a new matrix replacing all previous transform + snapshot.setTransform(currentTx); + } + + @LayoutlibDelegate + /*package*/ static void native_setMatrix(int nCanvas, int nMatrix) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + // get the current top graphics2D object. + GcSnapshot snapshot = canvasDelegate.getSnapshot(); + + // get the AffineTransform of the given matrix + AffineTransform matrixTx = matrixDelegate.getAffineTransform(); + + // give it to the graphics2D as a new matrix replacing all previous transform + snapshot.setTransform(matrixTx); + + if (matrixDelegate.hasPerspective()) { + assert false; + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE, + "android.graphics.Canvas#setMatrix(android.graphics.Matrix) only " + + "supports affine transformations.", null, null /*data*/); + } + } + + @LayoutlibDelegate + /*package*/ static boolean native_clipRect(int nCanvas, + float left, float top, + float right, float bottom, + int regionOp) { + + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return false; + } + + return canvasDelegate.clipRect(left, top, right, bottom, regionOp); + } + + @LayoutlibDelegate + /*package*/ static boolean native_clipPath(int nativeCanvas, + int nativePath, + int regionOp) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return true; + } + + Path_Delegate pathDelegate = Path_Delegate.getDelegate(nativePath); + if (pathDelegate == null) { + return true; + } + + return canvasDelegate.mSnapshot.clip(pathDelegate.getJavaShape(), regionOp); + } + + @LayoutlibDelegate + /*package*/ static boolean native_clipRegion(int nativeCanvas, + int nativeRegion, + int regionOp) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return true; + } + + Region_Delegate region = Region_Delegate.getDelegate(nativeRegion); + if (region == null) { + return true; + } + + return canvasDelegate.mSnapshot.clip(region.getJavaArea(), regionOp); + } + + @LayoutlibDelegate + /*package*/ static void nativeSetDrawFilter(int nativeCanvas, int nativeFilter) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.mDrawFilter = DrawFilter_Delegate.getDelegate(nativeFilter); + + if (canvasDelegate.mDrawFilter != null && + canvasDelegate.mDrawFilter.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_DRAWFILTER, + canvasDelegate.mDrawFilter.getSupportMessage(), null, null /*data*/); + } + } + + @LayoutlibDelegate + /*package*/ static boolean native_getClipBounds(int nativeCanvas, + Rect bounds) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return false; + } + + Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds(); + if (rect != null && rect.isEmpty() == false) { + bounds.left = rect.x; + bounds.top = rect.y; + bounds.right = rect.x + rect.width; + bounds.bottom = rect.y + rect.height; + return true; + } + + return false; + } + + @LayoutlibDelegate + /*package*/ static void native_getCTM(int canvas, int matrix) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); + if (canvasDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix); + if (matrixDelegate == null) { + return; + } + + AffineTransform transform = canvasDelegate.getSnapshot().getTransform(); + matrixDelegate.set(Matrix_Delegate.makeValues(transform)); + } + + @LayoutlibDelegate + /*package*/ static boolean native_quickReject(int nativeCanvas, + RectF rect, + int native_edgeType) { + // FIXME properly implement quickReject + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_quickReject(int nativeCanvas, + int path, + int native_edgeType) { + // FIXME properly implement quickReject + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_quickReject(int nativeCanvas, + float left, float top, + float right, float bottom, + int native_edgeType) { + // FIXME properly implement quickReject + return false; + } + + @LayoutlibDelegate + /*package*/ static void native_drawRGB(int nativeCanvas, int r, int g, int b) { + native_drawColor(nativeCanvas, 0xFF000000 | r << 16 | (g&0xFF) << 8 | (b&0xFF), + PorterDuff.Mode.SRC_OVER.nativeInt); + + } + + @LayoutlibDelegate + /*package*/ static void native_drawARGB(int nativeCanvas, int a, int r, int g, int b) { + native_drawColor(nativeCanvas, a << 24 | (r&0xFF) << 16 | (g&0xFF) << 8 | (b&0xFF), + PorterDuff.Mode.SRC_OVER.nativeInt); + } + + @LayoutlibDelegate + /*package*/ static void native_drawColor(int nativeCanvas, int color) { + native_drawColor(nativeCanvas, color, PorterDuff.Mode.SRC_OVER.nativeInt); + } + + @LayoutlibDelegate + /*package*/ static void native_drawColor(int nativeCanvas, final int color, final int mode) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + final int w = canvasDelegate.mBitmap.getImage().getWidth(); + final int h = canvasDelegate.mBitmap.getImage().getHeight(); + draw(nativeCanvas, new GcSnapshot.Drawable() { + + public void draw(Graphics2D graphics, Paint_Delegate paint) { + // reset its transform just in case + graphics.setTransform(new AffineTransform()); + + // set the color + graphics.setColor(new Color(color, true /*alpha*/)); + + Composite composite = PorterDuffXfermode_Delegate.getComposite( + PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF); + if (composite != null) { + graphics.setComposite(composite); + } + + graphics.fillRect(0, 0, w, h); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPaint(int nativeCanvas, int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPaint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawLine(int nativeCanvas, + final float startX, final float startY, final float stopX, final float stopY, + int paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawRect(int nativeCanvas, RectF rect, + int paint) { + native_drawRect(nativeCanvas, rect.left, rect.top, rect.right, rect.bottom, paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawRect(int nativeCanvas, + final float left, final float top, final float right, final float bottom, int paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + int style = paint.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillRect((int)left, (int)top, + (int)(right-left), (int)(bottom-top)); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawRect((int)left, (int)top, + (int)(right-left), (int)(bottom-top)); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawOval(int nativeCanvas, final RectF oval, int paint) { + if (oval.right > oval.left && oval.bottom > oval.top) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + int style = paint.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillOval((int)oval.left, (int)oval.top, + (int)oval.width(), (int)oval.height()); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawOval((int)oval.left, (int)oval.top, + (int)oval.width(), (int)oval.height()); + } + } + }); + } + } + + @LayoutlibDelegate + /*package*/ static void native_drawCircle(int nativeCanvas, + float cx, float cy, float radius, int paint) { + native_drawOval(nativeCanvas, + new RectF(cx - radius, cy - radius, radius*2, radius*2), + paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawArc(int nativeCanvas, + RectF oval, float startAngle, float sweep, boolean useCenter, int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawArc is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawRoundRect(int nativeCanvas, + final RectF rect, final float rx, final float ry, int paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + int style = paint.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillRoundRect( + (int)rect.left, (int)rect.top, + (int)rect.width(), (int)rect.height(), + (int)rx, (int)ry); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawRoundRect( + (int)rect.left, (int)rect.top, + (int)rect.width(), (int)rect.height(), + (int)rx, (int)ry); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPath(int nativeCanvas, int path, int paint) { + final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path); + if (pathDelegate == null) { + return; + } + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + Shape shape = pathDelegate.getJavaShape(); + int style = paint.getStyle(); + + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fill(shape); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.draw(shape); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(Canvas thisCanvas, int nativeCanvas, int bitmap, + float left, float top, + int nativePaintOrZero, + int canvasDensity, + int screenDensity, + int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + float right = left + image.getWidth(); + float bottom = top + image.getHeight(); + + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + (int)left, (int)top, (int)right, (int)bottom); + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(Canvas thisCanvas, int nativeCanvas, int bitmap, + Rect src, RectF dst, + int nativePaintOrZero, + int screenDensity, + int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + + if (src == null) { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom); + } else { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + src.left, src.top, src.width(), src.height(), + (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom); + } + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(int nativeCanvas, int bitmap, + Rect src, Rect dst, + int nativePaintOrZero, + int screenDensity, + int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + + if (src == null) { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + dst.left, dst.top, dst.right, dst.bottom); + } else { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + src.left, src.top, src.width(), src.height(), + dst.left, dst.top, dst.right, dst.bottom); + } + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(int nativeCanvas, int[] colors, + int offset, int stride, final float x, + final float y, int width, int height, + boolean hasAlpha, + int nativePaintOrZero) { + + // create a temp BufferedImage containing the content. + final BufferedImage image = new BufferedImage(width, height, + hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + image.setRGB(0, 0, width, height, colors, offset, stride); + + draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + graphics.drawImage(image, (int) x, (int) y, null); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void nativeDrawBitmapMatrix(int nCanvas, int nBitmap, + int nMatrix, int nPaint) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + // get the delegate from the native int, which can be null + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); + + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nBitmap); + if (bitmapDelegate == null) { + return; + } + + final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut); + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + final AffineTransform mtx = matrixDelegate.getAffineTransform(); + + canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + //FIXME add support for canvas, screen and bitmap densities. + graphics.drawImage(image, mtx, null); + } + }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeDrawBitmapMesh(int nCanvas, int nBitmap, + int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, + int colorOffset, int nPaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawBitmapMesh is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeDrawVertices(int nCanvas, int mode, int n, + float[] verts, int vertOffset, + float[] texs, int texOffset, + int[] colors, int colorOffset, + short[] indices, int indexOffset, + int indexCount, int nPaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawVertices is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawText(int nativeCanvas, + final char[] text, final int index, final int count, + final float startX, final float startY, int flags, int paint) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + // WARNING: the logic in this method is similar to Paint_Delegate.measureText. + // Any change to this method should be reflected in Paint.measureText + // Paint.TextAlign indicates how the text is positioned relative to X. + // LEFT is the default and there's nothing to do. + float x = startX; + float y = startY; + if (paint.getTextAlign() != Paint.Align.LEFT.nativeInt) { + float m = paint.measureText(text, index, count); + if (paint.getTextAlign() == Paint.Align.CENTER.nativeInt) { + x -= m / 2; + } else if (paint.getTextAlign() == Paint.Align.RIGHT.nativeInt) { + x -= m; + } + } + + List<FontInfo> fonts = paint.getFonts(); + + if (fonts.size() > 0) { + FontInfo mainFont = fonts.get(0); + int i = index; + int lastIndex = index + count; + while (i < lastIndex) { + // always start with the main font. + int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); + if (upTo == -1) { + // draw all the rest and exit. + graphics.setFont(mainFont.mFont); + graphics.drawChars(text, i, lastIndex - i, (int)x, (int)y); + return; + } else if (upTo > 0) { + // draw what's possible + graphics.setFont(mainFont.mFont); + graphics.drawChars(text, i, upTo - i, (int)x, (int)y); + + // compute the width that was drawn to increase x + x += mainFont.mMetrics.charsWidth(text, i, upTo - i); + + // move index to the first non displayed char. + i = upTo; + + // don't call continue at this point. Since it is certain the main font + // cannot display the font a index upTo (now ==i), we move on to the + // fallback fonts directly. + } + + // no char supported, attempt to read the next char(s) with the + // fallback font. In this case we only test the first character + // and then go back to test with the main font. + // Special test for 2-char characters. + boolean foundFont = false; + for (int f = 1 ; f < fonts.size() ; f++) { + FontInfo fontInfo = fonts.get(f); + + // need to check that the font can display the character. We test + // differently if the char is a high surrogate. + int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; + upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); + if (upTo == -1) { + // draw that char + graphics.setFont(fontInfo.mFont); + graphics.drawChars(text, i, charCount, (int)x, (int)y); + + // update x + x += fontInfo.mMetrics.charsWidth(text, i, charCount); + + // update the index in the text, and move on + i += charCount; + foundFont = true; + break; + + } + } + + // in case no font can display the char, display it with the main font. + // (it'll put a square probably) + if (foundFont == false) { + int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; + + graphics.setFont(mainFont.mFont); + graphics.drawChars(text, i, charCount, (int)x, (int)y); + + // measure it to advance x + x += mainFont.mMetrics.charsWidth(text, i, charCount); + + // and move to the next chars. + i += charCount; + } + } + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawText(int nativeCanvas, String text, + int start, int end, float x, + float y, int flags, int paint) { + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawTextRun(int nativeCanvas, String text, + int start, int end, int contextStart, int contextEnd, + float x, float y, int flags, int paint) { + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + native_drawText(nativeCanvas, buffer, start, end, x, y, flags, paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawTextRun(int nativeCanvas, char[] text, + int start, int count, int contextStart, int contextCount, + float x, float y, int flags, int paint) { + native_drawText(nativeCanvas, text, start, count, x, y, flags, paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPosText(int nativeCanvas, + char[] text, int index, + int count, float[] pos, + int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPosText is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPosText(int nativeCanvas, + String text, float[] pos, + int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPosText is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawTextOnPath(int nativeCanvas, + char[] text, int index, + int count, int path, + float hOffset, + float vOffset, int bidiFlags, + int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawTextOnPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawTextOnPath(int nativeCanvas, + String text, int path, + float hOffset, + float vOffset, + int flags, int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawTextOnPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPicture(int nativeCanvas, + int nativePicture) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPicture is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int nativeCanvas) { + // get the delegate from the native int so that it can be disposed. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.dispose(); + + // remove it from the manager. + sManager.removeJavaReferenceFor(nativeCanvas); + } + + // ---- Private delegate/helper methods ---- + + /** + * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. + * <p>Note that the drawable may actually be executed several times if there are + * layers involved (see {@link #saveLayer(RectF, int, int)}. + */ + private static void draw(int nCanvas, int nPaint, boolean compositeOnly, boolean forceSrcMode, + GcSnapshot.Drawable drawable) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + // get the paint which can be null if nPaint is 0; + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); + + canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode); + } + + /** + * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided + * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. + * <p>Note that the drawable may actually be executed several times if there are + * layers involved (see {@link #saveLayer(RectF, int, int)}. + */ + private static void draw(int nCanvas, GcSnapshot.Drawable drawable) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.mSnapshot.draw(drawable); + } + + private Canvas_Delegate(Bitmap_Delegate bitmap) { + mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap); + } + + private Canvas_Delegate() { + mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/); + } + + /** + * Disposes of the {@link Graphics2D} stack. + */ + private void dispose() { + mSnapshot.dispose(); + } + + private int save(int saveFlags) { + // get the current save count + int count = mSnapshot.size(); + + mSnapshot = mSnapshot.save(saveFlags); + + // return the old save count + return count; + } + + private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) { + Paint_Delegate paint = new Paint_Delegate(); + paint.setAlpha(alpha); + return saveLayer(rect, paint, saveFlags); + } + + private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) { + // get the current save count + int count = mSnapshot.size(); + + mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags); + + // return the old save count + return count; + } + + /** + * Restores the {@link GcSnapshot} to <var>saveCount</var> + * @param saveCount the saveCount + */ + private void restoreTo(int saveCount) { + mSnapshot = mSnapshot.restoreTo(saveCount); + } + + /** + * Restores the {@link GcSnapshot} to <var>saveCount</var> + * @param saveCount the saveCount + */ + private void restore() { + mSnapshot = mSnapshot.restore(); + } + + private boolean clipRect(float left, float top, float right, float bottom, int regionOp) { + return mSnapshot.clipRect(left, top, right, bottom, regionOp); + } + + private void setBitmap(Bitmap_Delegate bitmap) { + mBitmap = bitmap; + assert mSnapshot.size() == 1; + mSnapshot.setBitmap(mBitmap); + } + + private static void drawBitmap( + int nativeCanvas, + Bitmap_Delegate bitmap, + int nativePaintOrZero, + final int sleft, final int stop, final int sright, final int sbottom, + final int dleft, final int dtop, final int dright, final int dbottom) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the paint, which could be null if the int is 0 + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); + + final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut); + + draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0], + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + //FIXME add support for canvas, screen and bitmap densities. + graphics.drawImage(image, dleft, dtop, dright, dbottom, + sleft, stop, sright, sbottom, null); + } + }); + } + + + /** + * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate. + * The image returns, through a 1-size boolean array, whether the drawing code should + * use a SRC composite no matter what the paint says. + * + * @param bitmap the bitmap + * @param paint the paint that will be used to draw + * @param forceSrcMode whether the composite will have to be SRC + * @return the image to draw + */ + private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, + boolean[] forceSrcMode) { + BufferedImage image = bitmap.getImage(); + forceSrcMode[0] = false; + + // if the bitmap config is alpha_8, then we erase all color value from it + // before drawing it. + if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) { + fixAlpha8Bitmap(image); + } else if (bitmap.hasAlpha() == false) { + // hasAlpha is merely a rendering hint. There can in fact be alpha values + // in the bitmap but it should be ignored at drawing time. + // There is two ways to do this: + // - override the composite to be SRC. This can only be used if the composite + // was going to be SRC or SRC_OVER in the first place + // - Create a different bitmap to draw in which all the alpha channel values is set + // to 0xFF. + if (paint != null) { + Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); + if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) { + PorterDuff.Mode mode = + ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode(); + + forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || + mode == PorterDuff.Mode.SRC; + } + } + + // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB + if (forceSrcMode[0] == false) { + image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF); + } + } + + return image; + } + + private static void fixAlpha8Bitmap(final BufferedImage image) { + int w = image.getWidth(); + int h = image.getHeight(); + int[] argb = new int[w * h]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); + + final int length = argb.length; + for (int i = 0 ; i < length; i++) { + argb[i] &= 0xFF000000; + } + image.setRGB(0, 0, w, h, argb, 0, w); + } +} + diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java new file mode 100644 index 0000000..e5a7ab6 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.ColorFilter + * + * Through the layoutlib_create tool, the original native methods of ColorFilter 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 ColorFilter class. + * + * This also serve as a base class for all ColorFilter delegate classes. + * + * @see DelegateManager + * + */ +public abstract class ColorFilter_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<ColorFilter_Delegate> sManager = + new DelegateManager<ColorFilter_Delegate>(ColorFilter_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static ColorFilter_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance, int nativeColorFilter) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java new file mode 100644 index 0000000..2de344b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java @@ -0,0 +1,70 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.ColorMatrixColorFilter + * + * Through the layoutlib_create tool, the original native methods of ColorMatrixColorFilter 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 ColorMatrixColorFilter class. + * + * Because this extends {@link ColorFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link ColorFilter_Delegate}. + * + * @see ColorFilter_Delegate + * + */ +public class ColorMatrixColorFilter_Delegate extends ColorFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "ColorMatrix Color Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeColorMatrixFilter(float[] array) { + ColorMatrixColorFilter_Delegate newDelegate = new ColorMatrixColorFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nColorMatrixFilter(int nativeFilter, float[] array) { + // pass + return 0; + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java new file mode 100644 index 0000000..7c04a87 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.ComposePathEffect + * + * Through the layoutlib_create tool, the original native methods of ComposePathEffect 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 ComposePathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class ComposePathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Compose Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int outerpe, int innerpe) { + ComposePathEffect_Delegate newDelegate = new ComposePathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java deleted file mode 100644 index 863d64a..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java +++ /dev/null @@ -1,53 +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.Paint; - -/** A subclass of shader that returns the composition of two other shaders, combined by - an {@link android.graphics.Xfermode} subclass. -*/ -public class ComposeShader extends Shader { - /** Create a new compose shader, given shaders A, B, and a combining mode. - When the mode is applied, it will be given the result from shader A as its - "dst", and the result of from shader B as its "src". - @param shaderA The colors from this shader are seen as the "dst" by the mode - @param shaderB The colors from this shader are seen as the "src" by the mode - @param mode The mode that combines the colors from the two shaders. If mode - is null, then SRC_OVER is assumed. - */ - public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) { - // FIXME Implement shader - } - - /** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode. - When the mode is applied, it will be given the result from shader A as its - "dst", and the result of from shader B as its "src". - @param shaderA The colors from this shader are seen as the "dst" by the mode - @param shaderB The colors from this shader are seen as the "src" by the mode - @param mode The PorterDuff mode that combines the colors from the two shaders. - */ - public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) { - // FIXME Implement shader - } - - @Override - Paint getJavaPaint() { - return null; - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java new file mode 100644 index 0000000..f6e1d00 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java @@ -0,0 +1,97 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Paint; + +/** + * Delegate implementing the native methods of android.graphics.ComposeShader + * + * Through the layoutlib_create tool, the original native methods of ComposeShader 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 ComposeShader class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class ComposeShader_Delegate extends Shader_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Paint getJavaPaint() { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Compose Shaders are not supported in Layout Preview mode."; + } + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(int native_shaderA, int native_shaderB, + int native_mode) { + // FIXME not supported yet. + ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(int native_shaderA, int native_shaderB, + int porterDuffMode) { + // FIXME not supported yet. + ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate1(int native_shader, int native_skiaShaderA, + int native_skiaShaderB, int native_mode) { + // pass, not needed. + return 0; + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate2(int native_shader, int native_skiaShaderA, + int native_skiaShaderB, int porterDuffMode) { + // pass, not needed. + return 0; + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java new file mode 100644 index 0000000..b0f8168 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.CornerPathEffect + * + * Through the layoutlib_create tool, the original native methods of CornerPathEffect 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 CornerPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class CornerPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Corner Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(float radius) { + CornerPathEffect_Delegate newDelegate = new CornerPathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java deleted file mode 100644 index 46d4c70..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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; - -public class DashPathEffect extends PathEffect { - - private final float[] mIntervals; - private final float mPhase; - - /** - * The intervals array must contain an even number of entries (>=2), with - * the even indices specifying the "on" intervals, and the odd indices - * specifying the "off" intervals. phase is an offset into the intervals - * array (mod the sum of all of the intervals). The intervals array - * controls the length of the dashes. The paint's strokeWidth controls the - * thickness of the dashes. - * Note: this patheffect only affects drawing with the paint's style is set - * to STROKE or STROKE_AND_FILL. It is ignored if the drawing is done with - * style == FILL. - * @param intervals array of ON and OFF distances - * @param phase offset into the intervals array - */ - public DashPathEffect(float intervals[], float phase) { - if (intervals.length < 2) { - throw new ArrayIndexOutOfBoundsException(); - } - - mIntervals = intervals; - mPhase = phase; - } - - public float[] getIntervals() { - return mIntervals; - } - - public float getPhase() { - return mPhase; - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java new file mode 100644 index 0000000..d97c2ec --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.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 android.graphics; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.BasicStroke; +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.DashPathEffect + * + * Through the layoutlib_create tool, the original native methods of DashPathEffect 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 DashPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public final class DashPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + private final float[] mIntervals; + private final float mPhase; + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + return new BasicStroke( + paint.getStrokeWidth(), + paint.getJavaCap(), + paint.getJavaJoin(), + paint.getJavaStrokeMiter(), + mIntervals, + mPhase); + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public String getSupportMessage() { + // no message since isSupported returns true; + return null; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(float intervals[], float phase) { + DashPathEffect_Delegate newDelegate = new DashPathEffect_Delegate(intervals, phase); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- + + private DashPathEffect_Delegate(float intervals[], float phase) { + mIntervals = new float[intervals.length]; + System.arraycopy(intervals, 0, mIntervals, 0, intervals.length); + mPhase = phase; + } +} + diff --git a/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java new file mode 100644 index 0000000..ec4a810 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.DiscretePathEffect + * + * Through the layoutlib_create tool, the original native methods of DiscretePathEffect 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 DiscretePathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class DiscretePathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Discrete Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(float length, float deviation) { + DiscretePathEffect_Delegate newDelegate = new DiscretePathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java new file mode 100644 index 0000000..870c46b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.DrawFilter + * + * Through the layoutlib_create tool, the original native methods of DrawFilter 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 DrawFilter class. + * + * This also serve as a base class for all DrawFilter delegate classes. + * + * @see DelegateManager + * + */ +public abstract class DrawFilter_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<DrawFilter_Delegate> sManager = + new DelegateManager<DrawFilter_Delegate>(DrawFilter_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static DrawFilter_Delegate getDelegate(int nativeDrawFilter) { + return sManager.getDelegate(nativeDrawFilter); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int nativeDrawFilter) { + sManager.removeJavaReferenceFor(nativeDrawFilter); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java new file mode 100644 index 0000000..ebc1c1d --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.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 android.graphics; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.EmbossMaskFilter + * + * Through the layoutlib_create tool, the original native methods of EmbossMaskFilter 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 EmbossMaskFilter class. + * + * Because this extends {@link MaskFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link MaskFilter_Delegate}. + * + * @see MaskFilter_Delegate + * + */ +public class EmbossMaskFilter_Delegate extends MaskFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Emboss Mask Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor(float[] direction, float ambient, + float specular, float blurRadius) { + EmbossMaskFilter_Delegate newDelegate = new EmbossMaskFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/GradientShader.java b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java index 8c5a2a4..38c092d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/GradientShader.java +++ b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java @@ -16,23 +16,28 @@ package android.graphics; +import android.graphics.Shader.TileMode; /** - * Base class for Gradient shader. This is not a standard android class and is just used - * as a base class for the re-implemented gradient classes. - * - * It also provides a base class to handle common code between the different shaders' - * implementations of {@link java.awt.Paint}. - * - * @see LinearGradient - * @see RadialGradient - * @see SweepGradient + * Base class for true Gradient shader delegate. */ -public abstract class GradientShader extends Shader { +public abstract class Gradient_Delegate extends Shader_Delegate { protected final int[] mColors; protected final float[] mPositions; + @Override + public boolean isSupported() { + // all gradient shaders are supported. + return true; + } + + @Override + public String getSupportMessage() { + // all gradient shaders are supported, no need for a gradient support + return null; + } + /** * Creates the base shader and do some basic test on the parameters. * @@ -41,7 +46,7 @@ public abstract class GradientShader extends Shader { * corresponding color in the colors array. If this is null, the * the colors are distributed evenly along the gradient line. */ - protected GradientShader(int colors[], float positions[]) { + protected Gradient_Delegate(int colors[], float positions[]) { if (colors.length < 2) { throw new IllegalArgumentException("needs >= 2 number of colors"); } @@ -90,7 +95,7 @@ public abstract class GradientShader extends Shader { * Pre-computes the colors for the gradient. This must be called once before any call * to {@link #getGradientColor(float)} */ - protected synchronized void precomputeGradientColors() { + protected void precomputeGradientColors() { if (mGradient == null) { // actually create an array with an extra size, so that we can really go // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0 @@ -126,20 +131,23 @@ public abstract class GradientShader extends Shader { pos = 0.f; break; case REPEAT: - // remove the integer part to stay in the [0,1] range - // careful: this is a negative value, so use ceil instead of floor - pos = pos - (float)Math.ceil(pos); + // remove the integer part to stay in the [0,1] range. + // we also need to invert the value from [-1,0] to [0, 1] + pos = pos - (float)Math.floor(pos); break; case MIRROR: + // this is the same as the positive side, just make the value positive + // first. + pos = Math.abs(pos); + // get the integer and the decimal part - // careful: this is a negative value, so use ceil instead of floor - int intPart = (int)Math.ceil(pos); + int intPart = (int)Math.floor(pos); pos = pos - intPart; - // 0 -> -1 : mirrored order - // -1 -> -2: normal order + // 0 -> 1 : normal order + // 1 -> 2: mirrored // etc.. - // this means if the intpart is even we invert - if ((intPart % 2) == 0) { + // this means if the intpart is odd we invert + if ((intPart % 2) == 1) { pos = 1.f - pos; } break; @@ -199,7 +207,5 @@ public abstract class GradientShader extends Shader { private int computeChannel(int c1, int c2, float percent) { return c1 + (int)((percent * (c2-c1)) + .5); } - - } } diff --git a/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java new file mode 100644 index 0000000..51e0576 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java @@ -0,0 +1,69 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.LayerRasterizer + * + * Through the layoutlib_create tool, the original native methods of LayerRasterizer 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 LayerRasterizer class. + * + * Because this extends {@link Rasterizer_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link Rasterizer_Delegate}. + * + * @see Rasterizer_Delegate + * + */ +public class LayerRasterizer_Delegate extends Rasterizer_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Layer Rasterizers are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor() { + LayerRasterizer_Delegate newDelegate = new LayerRasterizer_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void nativeAddLayer(int native_layer, int native_paint, float dx, float dy) { + + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java new file mode 100644 index 0000000..0ee883d --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java @@ -0,0 +1,70 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.LightingColorFilter + * + * Through the layoutlib_create tool, the original native methods of LightingColorFilter 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 LightingColorFilter class. + * + * Because this extends {@link ColorFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link ColorFilter_Delegate}. + * + * @see ColorFilter_Delegate + * + */ +public class LightingColorFilter_Delegate extends ColorFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Lighting Color Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int native_CreateLightingFilter(int mul, int add) { + LightingColorFilter_Delegate newDelegate = new LightingColorFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nCreateLightingFilter(int nativeFilter, int mul, int add) { + // pass + return 0; + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java deleted file mode 100644 index 10c4a5e..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java +++ /dev/null @@ -1,173 +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; - -public class LinearGradient extends GradientShader { - - private java.awt.Paint mJavaPaint; - - /** - * Create a shader that draws a linear gradient along a line. - * - * @param x0 The x-coordinate for the start of the gradient line - * @param y0 The y-coordinate for the start of the gradient line - * @param x1 The x-coordinate for the end of the gradient line - * @param y1 The y-coordinate for the end of the gradient line - * @param colors The colors to be distributed along the gradient line - * @param positions May be null. The relative positions [0..1] of each - * corresponding color in the colors array. If this is null, the - * the colors are distributed evenly along the gradient line. - * @param tile The Shader tiling mode - */ - public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[], - TileMode tile) { - super(colors, positions); - mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile); - } - - /** - * Create a shader that draws a linear gradient along a line. - * - * @param x0 The x-coordinate for the start of the gradient line - * @param y0 The y-coordinate for the start of the gradient line - * @param x1 The x-coordinate for the end of the gradient line - * @param y1 The y-coordinate for the end of the gradient line - * @param color0 The color at the start of the gradient line. - * @param color1 The color at the end of the gradient line. - * @param tile The Shader tiling mode - */ - public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, - TileMode tile) { - this(x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/, tile); - } - - // ---------- Custom Methods - - @Override - java.awt.Paint getJavaPaint() { - return mJavaPaint; - } - - /** - * Linear Gradient (Java) Paint able to handle more than 2 points, as - * {@link java.awt.GradientPaint} only supports 2 points and does not support Android's tile - * modes. - */ - private static class LinearGradientPaint extends GradientPaint { - - private final float mX0; - private final float mY0; - private final float mDx; - private final float mDy; - private final float mDSize2; - - public LinearGradientPaint(float x0, float y0, float x1, float y1, int colors[], - float positions[], TileMode tile) { - super(colors, positions, tile); - mX0 = x0; - mY0 = y0; - mDx = x1 - x0; - mDy = y1 - y0; - mDSize2 = mDx * mDx + mDy * mDy; - } - - public java.awt.PaintContext createContext( - java.awt.image.ColorModel colorModel, - java.awt.Rectangle deviceBounds, - java.awt.geom.Rectangle2D userBounds, - java.awt.geom.AffineTransform xform, - java.awt.RenderingHints hints) { - precomputeGradientColors(); - return new LinearGradientPaintContext(colorModel); - } - - private class LinearGradientPaintContext implements java.awt.PaintContext { - - private final java.awt.image.ColorModel mColorModel; - - public LinearGradientPaintContext(java.awt.image.ColorModel colorModel) { - mColorModel = colorModel; - // FIXME: so far all this is always the same rect gotten in getRaster with an indentity matrix? - } - - public void dispose() { - } - - public java.awt.image.ColorModel getColorModel() { - return mColorModel; - } - - public java.awt.image.Raster getRaster(int x, int y, int w, int h) { - java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, - java.awt.image.BufferedImage.TYPE_INT_ARGB); - - int[] data = new int[w*h]; - - if (mDx == 0) { // vertical gradient - // compute first column and copy to all other columns - int index = 0; - for (int iy = 0 ; iy < h ; iy++) { - int color = getColor(iy + y, mY0, mDy); - for (int ix = 0 ; ix < w ; ix++) { - data[index++] = color; - } - } - } else if (mDy == 0) { // horizontal - // compute first line in a tmp array and copy to all lines - int[] line = new int[w]; - for (int ix = 0 ; ix < w ; ix++) { - line[ix] = getColor(ix + x, mX0, mDx); - } - - for (int iy = 0 ; iy < h ; iy++) { - System.arraycopy(line, 0, data, iy*w, line.length); - } - } else { - int index = 0; - for (int iy = 0 ; iy < h ; iy++) { - for (int ix = 0 ; ix < w ; ix++) { - data[index++] = getColor(ix + x, iy + y); - } - } - } - - image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); - - return image.getRaster(); - } - } - - /** Returns a color for the easy vertical/horizontal mode */ - private int getColor(float absPos, float refPos, float refSize) { - float pos = (absPos - refPos) / refSize; - - return getGradientColor(pos); - } - - /** - * Returns a color for an arbitrary point. - */ - private int getColor(float x, float y) { - // find the x position on the gradient vector. - float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2; - // from it get the position relative to the vector - float pos = (float) ((_x - mX0) / mDx); - - return getGradientColor(pos); - } - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java new file mode 100644 index 0000000..a2ba758 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java @@ -0,0 +1,236 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.LinearGradient + * + * Through the layoutlib_create tool, the original native methods of LinearGradient 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 LinearGradient class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public final class LinearGradient_Delegate extends Gradient_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(LinearGradient thisGradient, + float x0, float y0, float x1, float y1, + int colors[], float positions[], int tileMode) { + LinearGradient_Delegate newDelegate = new LinearGradient_Delegate(x0, y0, x1, y1, + colors, positions, Shader_Delegate.getTileMode(tileMode)); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(LinearGradient thisGradient, + float x0, float y0, float x1, float y1, + int color0, int color1, int tileMode) { + return nativeCreate1(thisGradient, + x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/, + tileMode); + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate1(LinearGradient thisGradient, + int native_shader, float x0, float y0, float x1, float y1, + int colors[], float positions[], int tileMode) { + // nothing to be done here. + return 0; + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate2(LinearGradient thisGradient, + int native_shader, float x0, float y0, float x1, float y1, + int color0, int color1, int tileMode) { + // nothing to be done here. + return 0; + } + + // ---- Private delegate/helper methods ---- + + /** + * Create a shader that draws a linear gradient along a line. + * + * @param x0 The x-coordinate for the start of the gradient line + * @param y0 The y-coordinate for the start of the gradient line + * @param x1 The x-coordinate for the end of the gradient line + * @param y1 The y-coordinate for the end of the gradient line + * @param colors The colors to be distributed along the gradient line + * @param positions May be null. The relative positions [0..1] of each + * corresponding color in the colors array. If this is null, the + * the colors are distributed evenly along the gradient line. + * @param tile The Shader tiling mode + */ + private LinearGradient_Delegate(float x0, float y0, float x1, float y1, + int colors[], float positions[], TileMode tile) { + super(colors, positions); + mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile); + } + + // ---- Custom Java Paint ---- + /** + * Linear Gradient (Java) Paint able to handle more than 2 points, as + * {@link java.awt.GradientPaint} only supports 2 points and does not support Android's tile + * modes. + */ + private class LinearGradientPaint extends GradientPaint { + + private final float mX0; + private final float mY0; + private final float mDx; + private final float mDy; + private final float mDSize2; + + public LinearGradientPaint(float x0, float y0, float x1, float y1, int colors[], + float positions[], TileMode tile) { + super(colors, positions, tile); + mX0 = x0; + mY0 = y0; + mDx = x1 - x0; + mDy = y1 - y0; + mDSize2 = mDx * mDx + mDy * mDy; + } + + public java.awt.PaintContext createContext( + java.awt.image.ColorModel colorModel, + java.awt.Rectangle deviceBounds, + java.awt.geom.Rectangle2D userBounds, + java.awt.geom.AffineTransform xform, + java.awt.RenderingHints hints) { + precomputeGradientColors(); + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in LinearGradient", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in LinearGradient", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new LinearGradientPaintContext(canvasMatrix, localMatrix, colorModel); + } + + private class LinearGradientPaintContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + private LinearGradientPaintContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + mColorModel = colorModel; + } + + public void dispose() { + } + + public java.awt.image.ColorModel getColorModel() { + return mColorModel; + } + + public java.awt.image.Raster getRaster(int x, int y, int w, int h) { + java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, + java.awt.image.BufferedImage.TYPE_INT_ARGB); + + int[] data = new int[w*h]; + + int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix. + pt1[0] = pt2[0]; + pt1[1] = pt2[1]; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + data[index++] = getColor(pt2[0], pt2[1]); + } + } + + image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); + + return image.getRaster(); + } + } + + /** + * Returns a color for an arbitrary point. + */ + private int getColor(float x, float y) { + float pos; + if (mDx == 0) { + pos = (y - mY0) / mDy; + } else if (mDy == 0) { + pos = (x - mX0) / mDx; + } else { + // find the x position on the gradient vector. + float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2; + // from it get the position relative to the vector + pos = (_x - mX0) / mDx; + } + + return getGradientColor(pos); + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java new file mode 100644 index 0000000..c2f27e4 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.MaskFilter + * + * Through the layoutlib_create tool, the original native methods of MaskFilter 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 MaskFilter class. + * + * This also serve as a base class for all MaskFilter delegate classes. + * + * @see DelegateManager + * + */ +public abstract class MaskFilter_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<MaskFilter_Delegate> sManager = + new DelegateManager<MaskFilter_Delegate>(MaskFilter_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static MaskFilter_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_filter) { + sManager.removeJavaReferenceFor(native_filter); + } + + // ---- Private delegate/helper methods ---- +} 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..451edd2 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java @@ -0,0 +1,1129 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +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>(Matrix_Delegate.class); + + // ---- delegate data ---- + private float mValues[] = new float[MATRIX_SIZE]; + + // ---- Public Helper methods ---- + + public static Matrix_Delegate getDelegate(int native_instance) { + return sManager.getDelegate(native_instance); + } + + /** + * 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) { + return null; + } + + return delegate.getAffineTransform(); + } + + public static boolean hasPerspective(Matrix m) { + Matrix_Delegate delegate = sManager.getDelegate(m.native_instance); + if (delegate == null) { + return false; + } + + return delegate.hasPerspective(); + } + + /** + * Sets the content of the matrix with the content of another matrix. + */ + public void set(Matrix_Delegate matrix) { + System.arraycopy(matrix.mValues, 0, mValues, 0, MATRIX_SIZE); + } + + /** + * Sets the content of the matrix with the content of another matrix represented as an array + * of values. + */ + public void set(float[] values) { + System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE); + } + + /** + * Resets the matrix to be the identity matrix. + */ + public void reset() { + reset(mValues); + } + + /** + * Returns whether or not the matrix is identity. + */ + 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; + } + + public static float[] makeValues(AffineTransform matrix) { + float[] values = new float[MATRIX_SIZE]; + values[0] = (float) matrix.getScaleX(); + values[1] = (float) matrix.getShearX(); + values[2] = (float) matrix.getTranslateX(); + values[3] = (float) matrix.getShearY(); + values[4] = (float) matrix.getScaleY(); + values[5] = (float) matrix.getTranslateY(); + values[6] = 0.f; + values[7] = 0.f; + values[8] = 1.f; + + return values; + } + + public static Matrix_Delegate make(AffineTransform matrix) { + return new Matrix_Delegate(makeValues(matrix)); + } + + public boolean mapRect(RectF dst, RectF src) { + // 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 (computeTypeMask() & kRectStaysRect_Mask) != 0; + } + + + /** + * Returns an {@link AffineTransform} matching the matrix. + */ + public AffineTransform getAffineTransform() { + return getAffineTransform(mValues); + } + + public boolean hasPerspective() { + return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1); + } + + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ 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.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static boolean native_isIdentity(int native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + return d.isIdentity(); + } + + @LayoutlibDelegate + /*package*/ static boolean native_rectStaysRect(int native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return true; + } + + return (d.computeTypeMask() & kRectStaysRect_Mask) != 0; + } + + @LayoutlibDelegate + /*package*/ static void native_reset(int native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + reset(d.mValues); + } + + @LayoutlibDelegate + /*package*/ static void native_set(int native_object, int other) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + Matrix_Delegate src = sManager.getDelegate(other); + if (src == null) { + return; + } + + System.arraycopy(src.mValues, 0, d.mValues, 0, MATRIX_SIZE); + } + + @LayoutlibDelegate + /*package*/ static void native_setTranslate(int native_object, float dx, float dy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + setTranslate(d.mValues, dx, dy); + } + + @LayoutlibDelegate + /*package*/ 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) { + return; + } + + d.mValues = getScale(sx, sy, px, py); + } + + @LayoutlibDelegate + /*package*/ static void native_setScale(int native_object, float sx, float sy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + 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; + } + + @LayoutlibDelegate + /*package*/ static void native_setRotate(int native_object, float degrees, float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues = getRotate(degrees, px, py); + } + + @LayoutlibDelegate + /*package*/ static void native_setRotate(int native_object, float degrees) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + setRotate(d.mValues, degrees); + } + + @LayoutlibDelegate + /*package*/ 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) { + 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)); + } + + @LayoutlibDelegate + /*package*/ static void native_setSinCos(int native_object, float sinValue, float cosValue) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + setRotate(d.mValues, sinValue, cosValue); + } + + @LayoutlibDelegate + /*package*/ 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) { + return; + } + + d.mValues = getSkew(kx, ky, px, py); + } + + @LayoutlibDelegate + /*package*/ static void native_setSkew(int native_object, float kx, float ky) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + 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; + } + + @LayoutlibDelegate + /*package*/ 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) { + return false; + } + + Matrix_Delegate a_mtx = sManager.getDelegate(a); + if (a_mtx == null) { + return false; + } + + Matrix_Delegate b_mtx = sManager.getDelegate(b); + if (b_mtx == null) { + return false; + } + + multiply(d.mValues, a_mtx.mValues, b_mtx.mValues); + + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preTranslate(int native_object, float dx, float dy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getTranslate(dx, dy)); + return true; + } + + @LayoutlibDelegate + /*package*/ 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) { + return false; + } + + d.preTransform(getScale(sx, sy, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preScale(int native_object, float sx, float sy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getScale(sx, sy)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preRotate(int native_object, float degrees, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getRotate(degrees, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preRotate(int native_object, float degrees) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + 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; + } + + @LayoutlibDelegate + /*package*/ 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) { + return false; + } + + d.preTransform(getSkew(kx, ky, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preSkew(int native_object, float kx, float ky) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getSkew(kx, ky)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preConcat(int native_object, int other_matrix) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate other = sManager.getDelegate(other_matrix); + if (d == null) { + return false; + } + + d.preTransform(other.mValues); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postTranslate(int native_object, float dx, float dy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getTranslate(dx, dy)); + return true; + } + + @LayoutlibDelegate + /*package*/ 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) { + return false; + } + + d.postTransform(getScale(sx, sy, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postScale(int native_object, float sx, float sy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getScale(sx, sy)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postRotate(int native_object, float degrees, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getRotate(degrees, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postRotate(int native_object, float degrees) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getRotate(degrees)); + return true; + } + + @LayoutlibDelegate + /*package*/ 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) { + return false; + } + + d.postTransform(getSkew(kx, ky, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postSkew(int native_object, float kx, float ky) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getSkew(kx, ky)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postConcat(int native_object, int other_matrix) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate other = sManager.getDelegate(other_matrix); + if (d == null) { + return false; + } + + d.postTransform(other.mValues); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_setRectToRect(int native_object, RectF src, + RectF dst, int stf) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + 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; + } + + @LayoutlibDelegate + /*package*/ static boolean native_setPolyToPoly(int native_object, float[] src, int srcIndex, + float[] dst, int dstIndex, int pointCount) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Matrix.setPolyToPoly is not supported.", + null, null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_invert(int native_object, int inverse) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate inv_mtx = sManager.getDelegate(inverse); + if (inv_mtx == null) { + return false; + } + + try { + AffineTransform affineTransform = d.getAffineTransform(); + 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; + } + } + + @LayoutlibDelegate + /*package*/ 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) { + return; + } + + if (isPts) { + d.mapPoints(dst, dstIndex, src, srcIndex, ptCount); + } else { + d.mapVectors(dst, dstIndex, src, srcIndex, ptCount); + } + } + + @LayoutlibDelegate + /*package*/ static boolean native_mapRect(int native_object, RectF dst, RectF src) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + return d.mapRect(dst, src); + } + + @LayoutlibDelegate + /*package*/ static float native_mapRadius(int native_object, float radius) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return 0.f; + } + + float[] src = new float[] { radius, 0.f, 0.f, radius }; + d.mapVectors(src, 0, src, 0, 2); + + float l1 = getPointLength(src, 0); + float l2 = getPointLength(src, 2); + + return (float) Math.sqrt(l1 * l2); + } + + @LayoutlibDelegate + /*package*/ static void native_getValues(int native_object, float[] values) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + System.arraycopy(d.mValues, 0, d.mValues, 0, MATRIX_SIZE); + } + + @LayoutlibDelegate + /*package*/ static void native_setValues(int native_object, float[] values) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + System.arraycopy(values, 0, d.mValues, 0, MATRIX_SIZE); + } + + @LayoutlibDelegate + /*package*/ static boolean native_equals(int native_a, int native_b) { + Matrix_Delegate a = sManager.getDelegate(native_a); + if (a == null) { + return false; + } + + Matrix_Delegate b = sManager.getDelegate(native_b); + if (b == null) { + return false; + } + + for (int i = 0 ; i < MATRIX_SIZE ; i++) { + if (a.mValues[i] != b.mValues[i]) { + return false; + } + } + + return true; + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private helper methods ---- + + /*package*/ static AffineTransform getAffineTransform(float[] matrix) { + // 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( + matrix[0], matrix[3], matrix[1], + matrix[4], matrix[2], matrix[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; + } + + private Matrix_Delegate() { + reset(); + } + + private Matrix_Delegate(float[] values) { + System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE); + } + + /** + * 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) { + final int count = pointCount * 2; + + float[] tmpDest = dst; + boolean inPlace = dst == src; + if (inPlace) { + tmpDest = new float[dstIndex + count]; + } + + for (int i = 0 ; i < count ; i += 2) { + // 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, count); + } + } + + /** + * 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); + } + + private void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int ptCount) { + if (hasPerspective()) { + // transform the (0,0) point + float[] origin = new float[] { 0.f, 0.f}; + mapPoints(origin); + + // translate the vector data as points + mapPoints(dst, dstIndex, src, srcIndex, ptCount); + + // then substract the transformed origin. + final int count = ptCount * 2; + for (int i = 0 ; i < count ; i += 2) { + dst[dstIndex + i] = dst[dstIndex + i] - origin[0]; + dst[dstIndex + i + 1] = dst[dstIndex + i + 1] - origin[1]; + } + } else { + // make a copy of the matrix + Matrix_Delegate copy = new Matrix_Delegate(mValues); + + // remove the translation + setTranslate(copy.mValues, 0, 0); + + // map the content as points. + copy.mapPoints(dst, dstIndex, src, srcIndex, ptCount); + } + } + + private static float getPointLength(float[] src, int index) { + return (float) Math.sqrt(src[index] * src[index] + src[index + 1] * src[index + 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. + */ + /*package*/ 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 + */ + /*package*/ static float[] getTranslate(float dx, float dy) { + return setTranslate(new float[9], dx, dy); + } + + /*package*/ 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; + } + + /*package*/ 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 + */ + /*package*/ 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; + } + + + /*package*/ 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); + } + + /*package*/ static float[] getRotate(float sin, float cos) { + return setRotate(new float[9], sin, cos); + } + + /*package*/ 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); + } + + /*package*/ 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; + } + + /*package*/ 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; + } + + /*package*/ static float[] getSkew(float kx, float ky) { + return new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }; + } + + /*package*/ 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/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java new file mode 100644 index 0000000..5e882ce --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java @@ -0,0 +1,225 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.GcSnapshot; +import com.android.ninepatch.NinePatchChunk; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.drawable.NinePatchDrawable; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.Map; + +/** + * Delegate implementing the native methods of android.graphics.NinePatch + * + * Through the layoutlib_create tool, the original native methods of NinePatch have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +public final class NinePatch_Delegate { + + /** + * Cache map for {@link NinePatchChunk}. + * When the chunks are created they are serialized into a byte[], and both are put + * in the cache, using a {@link SoftReference} for the chunk. The default Java classes + * for {@link NinePatch} and {@link NinePatchDrawable} only reference to the byte[] data, and + * provide this for drawing. + * Using the cache map allows us to not have to deserialize the byte[] back into a + * {@link NinePatchChunk} every time a rendering is done. + */ + private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache = + new HashMap<byte[], SoftReference<NinePatchChunk>>(); + + // ---- Public Helper methods ---- + + /** + * Serializes the given chunk. + * + * @return the serialized data for the chunk. + */ + public static byte[] serialize(NinePatchChunk chunk) { + // serialize the chunk to get a byte[] + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = null; + try { + oos = new ObjectOutputStream(baos); + oos.writeObject(chunk); + } catch (IOException e) { + Bridge.getLog().error(null, "Failed to serialize NinePatchChunk.", e, null /*data*/); + return null; + } finally { + if (oos != null) { + try { + oos.close(); + } catch (IOException e) { + } + } + } + + // get the array and add it to the cache + byte[] array = baos.toByteArray(); + sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk)); + return array; + } + + /** + * Returns a {@link NinePatchChunk} object for the given serialized representation. + * + * If the chunk is present in the cache then the object from the cache is returned, otherwise + * the array is deserialized into a {@link NinePatchChunk} object. + * + * @param array the serialized representation of the chunk. + * @return the NinePatchChunk or null if deserialization failed. + */ + public static NinePatchChunk getChunk(byte[] array) { + SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array); + NinePatchChunk chunk = chunkRef.get(); + if (chunk == null) { + ByteArrayInputStream bais = new ByteArrayInputStream(array); + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(bais); + chunk = (NinePatchChunk) ois.readObject(); + + // put back the chunk in the cache + if (chunk != null) { + sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk)); + } + } catch (IOException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to deserialize NinePatchChunk content.", e, null /*data*/); + return null; + } catch (ClassNotFoundException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to deserialize NinePatchChunk class.", e, null /*data*/); + return null; + } finally { + if (ois != null) { + try { + ois.close(); + } catch (IOException e) { + } + } + } + } + + return chunk; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static boolean isNinePatchChunk(byte[] chunk) { + NinePatchChunk chunkObject = getChunk(chunk); + if (chunkObject != null) { + return true; + } + + return false; + } + + @LayoutlibDelegate + /*package*/ static void validateNinePatchChunk(int bitmap, byte[] chunk) { + // the default JNI implementation only checks that the byte[] has the same + // size as the C struct it represent. Since we cannot do the same check (serialization + // will return different size depending on content), we do nothing. + } + + @LayoutlibDelegate + /*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance, + byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) { + draw(canvas_instance, + (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(), + bitmap_instance, c, paint_instance_or_null, + destDensity, srcDensity); + } + + @LayoutlibDelegate + /*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance, + byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) { + draw(canvas_instance, + loc.left, loc.top, loc.width(), loc.height(), + bitmap_instance, c, paint_instance_or_null, + destDensity, srcDensity); + } + + @LayoutlibDelegate + /*package*/ static int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location) { + return 0; + } + + // ---- Private Helper methods ---- + + private static void draw(int canvas_instance, + final int left, final int top, final int right, final int bottom, + int bitmap_instance, byte[] c, int paint_instance_or_null, + final int destDensity, final int srcDensity) { + // get the delegate from the native int. + final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance); + if (bitmap_delegate == null) { + return; + } + + if (c == null) { + // not a 9-patch? + BufferedImage image = bitmap_delegate.getImage(); + Canvas_Delegate.native_drawBitmap(canvas_instance, bitmap_instance, + new Rect(0, 0, image.getWidth(), image.getHeight()), + new Rect(left, top, right, bottom), + paint_instance_or_null, destDensity, srcDensity); + return; + } + + final NinePatchChunk chunkObject = getChunk(c); + assert chunkObject != null; + if (chunkObject == null) { + return; + } + + Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance); + if (canvas_delegate == null) { + return; + } + + // this one can be null + Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null); + + canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + chunkObject.draw(bitmap_delegate.getImage(), graphics, + left, top, right - left, bottom - top, destDensity, srcDensity); + } + }, paint_delegate, true /*compositeOnly*/, false /*forceSrcMode*/); + + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java deleted file mode 100644 index d13b5fe..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Paint.java +++ /dev/null @@ -1,1211 +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 android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.SpannedString; -import android.text.TextUtils; - -import java.awt.BasicStroke; -import java.awt.Font; -import java.awt.Toolkit; -import java.awt.font.FontRenderContext; -import java.awt.geom.AffineTransform; -import java.awt.geom.Rectangle2D; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * A paint implementation overridden by the LayoutLib bridge. - */ -public class Paint extends _Original_Paint { - - private int mColor = 0xFFFFFFFF; - private float mStrokeWidth = 1.f; - private float mTextSize = 20; - private float mScaleX = 1; - private float mSkewX = 0; - private Align mAlign = Align.LEFT; - private Style mStyle = Style.FILL; - private float mStrokeMiter = 4.0f; - private Cap mCap = Cap.BUTT; - private Join mJoin = Join.MITER; - private int mFlags = 0; - - /** - * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. - */ - public static final class FontInfo { - Font mFont; - java.awt.FontMetrics mMetrics; - } - - private List<FontInfo> mFonts; - private final FontRenderContext mFontContext = new FontRenderContext( - new AffineTransform(), true, true); - - public static final int ANTI_ALIAS_FLAG = _Original_Paint.ANTI_ALIAS_FLAG; - public static final int FILTER_BITMAP_FLAG = _Original_Paint.FILTER_BITMAP_FLAG; - public static final int DITHER_FLAG = _Original_Paint.DITHER_FLAG; - public static final int UNDERLINE_TEXT_FLAG = _Original_Paint.UNDERLINE_TEXT_FLAG; - public static final int STRIKE_THRU_TEXT_FLAG = _Original_Paint.STRIKE_THRU_TEXT_FLAG; - public static final int FAKE_BOLD_TEXT_FLAG = _Original_Paint.FAKE_BOLD_TEXT_FLAG; - public static final int LINEAR_TEXT_FLAG = _Original_Paint.LINEAR_TEXT_FLAG; - public static final int SUBPIXEL_TEXT_FLAG = _Original_Paint.SUBPIXEL_TEXT_FLAG; - public static final int DEV_KERN_TEXT_FLAG = _Original_Paint.DEV_KERN_TEXT_FLAG; - - public static class FontMetrics extends _Original_Paint.FontMetrics { - } - - public static class FontMetricsInt extends _Original_Paint.FontMetricsInt { - } - - /** - * The Style specifies if the primitive being drawn is filled, - * stroked, or both (in the same color). The default is FILL. - */ - public enum Style { - /** - * Geometry and text drawn with this style will be filled, ignoring all - * stroke-related settings in the paint. - */ - FILL (0), - /** - * Geometry and text drawn with this style will be stroked, respecting - * the stroke-related fields on the paint. - */ - STROKE (1), - /** - * Geometry and text drawn with this style will be both filled and - * stroked at the same time, respecting the stroke-related fields on - * the paint. - */ - FILL_AND_STROKE (2); - - Style(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - /** - * The Cap specifies the treatment for the beginning and ending of - * stroked lines and paths. The default is BUTT. - */ - public enum Cap { - /** - * The stroke ends with the path, and does not project beyond it. - */ - BUTT (0), - /** - * The stroke projects out as a square, with the center at the end - * of the path. - */ - ROUND (1), - /** - * The stroke projects out as a semicircle, with the center at the - * end of the path. - */ - SQUARE (2); - - private Cap(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - - /** custom for layoutlib */ - public int getJavaCap() { - switch (this) { - case BUTT: - return BasicStroke.CAP_BUTT; - case ROUND: - return BasicStroke.CAP_ROUND; - default: - case SQUARE: - return BasicStroke.CAP_SQUARE; - } - } - } - - /** - * The Join specifies the treatment where lines and curve segments - * join on a stroked path. The default is MITER. - */ - public enum Join { - /** - * The outer edges of a join meet at a sharp angle - */ - MITER (0), - /** - * The outer edges of a join meet in a circular arc. - */ - ROUND (1), - /** - * The outer edges of a join meet with a straight line - */ - BEVEL (2); - - private Join(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - - /** custom for layoutlib */ - public int getJavaJoin() { - switch (this) { - default: - case MITER: - return BasicStroke.JOIN_MITER; - case ROUND: - return BasicStroke.JOIN_ROUND; - case BEVEL: - return BasicStroke.JOIN_BEVEL; - } - } - } - - /** - * Align specifies how drawText aligns its text relative to the - * [x,y] coordinates. The default is LEFT. - */ - public enum Align { - /** - * The text is drawn to the right of the x,y origin - */ - LEFT (0), - /** - * The text is drawn centered horizontally on the x,y origin - */ - CENTER (1), - /** - * The text is drawn to the left of the x,y origin - */ - RIGHT (2); - - private Align(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - public Paint() { - this(0); - } - - /* - * Do not remove or com.android.layoutlib.bridge.TestClassReplacement fails. - */ - @Override - public void finalize() { } - - public Paint(int flags) { - setFlags(flags | DEFAULT_PAINT_FLAGS); - initFont(); - } - - public Paint(Paint paint) { - set(paint); - initFont(); - } - - @Override - public void reset() { - super.reset(); - } - - /** - * Returns the list of {@link Font} objects. The first item is the main font, the rest - * are fall backs for characters not present in the main font. - */ - public List<FontInfo> getFonts() { - return mFonts; - } - - private void initFont() { - mTypeface = Typeface.DEFAULT; - updateFontObject(); - } - - /** - * Update the {@link Font} object from the typeface, text size and scaling - */ - @SuppressWarnings("deprecation") - private void updateFontObject() { - if (mTypeface != null) { - // Get the fonts from the TypeFace object. - List<Font> fonts = mTypeface.getFonts(); - - // create new font objects as well as FontMetrics, based on the current text size - // and skew info. - ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size()); - for (Font font : fonts) { - FontInfo info = new FontInfo(); - info.mFont = font.deriveFont(mTextSize); - if (mScaleX != 1.0 || mSkewX != 0) { - // TODO: support skew - info.mFont = info.mFont.deriveFont(new AffineTransform( - mScaleX, mSkewX, 0, 0, 1, 0)); - } - info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont); - - infoList.add(info); - } - - mFonts = Collections.unmodifiableList(infoList); - } - } - - //---------------------------------------- - - public void set(Paint src) { - if (this != src) { - mColor = src.mColor; - mTextSize = src.mTextSize; - mScaleX = src.mScaleX; - mSkewX = src.mSkewX; - mAlign = src.mAlign; - mStyle = src.mStyle; - mFlags = src.mFlags; - - updateFontObject(); - - super.set(src); - } - } - - @Override - public void setCompatibilityScaling(float factor) { - super.setCompatibilityScaling(factor); - } - - @Override - public int getFlags() { - return mFlags; - } - - @Override - public void setFlags(int flags) { - mFlags = flags; - } - - @Override - public boolean isAntiAlias() { - return super.isAntiAlias(); - } - - @Override - public boolean isDither() { - return super.isDither(); - } - - @Override - public boolean isLinearText() { - return super.isLinearText(); - } - - @Override - public boolean isStrikeThruText() { - return super.isStrikeThruText(); - } - - @Override - public boolean isUnderlineText() { - return super.isUnderlineText(); - } - - @Override - public boolean isFakeBoldText() { - return super.isFakeBoldText(); - } - - @Override - public boolean isSubpixelText() { - return super.isSubpixelText(); - } - - @Override - public boolean isFilterBitmap() { - return super.isFilterBitmap(); - } - - /** - * Return the font's recommended interline spacing, given the Paint's - * settings for typeface, textSize, etc. If metrics is not null, return the - * fontmetric values in it. - * - * @param metrics If this object is not null, its fields are filled with - * the appropriate values given the paint's text attributes. - * @return the font's recommended interline spacing. - */ - public float getFontMetrics(FontMetrics metrics) { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - if (metrics != null) { - // Android expects negative ascent so we invert the value from Java. - metrics.top = - javaMetrics.getMaxAscent(); - metrics.ascent = - javaMetrics.getAscent(); - metrics.descent = javaMetrics.getDescent(); - metrics.bottom = javaMetrics.getMaxDescent(); - metrics.leading = javaMetrics.getLeading(); - } - - return javaMetrics.getHeight(); - } - - return 0; - } - - public int getFontMetricsInt(FontMetricsInt metrics) { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - if (metrics != null) { - // Android expects negative ascent so we invert the value from Java. - metrics.top = - javaMetrics.getMaxAscent(); - metrics.ascent = - javaMetrics.getAscent(); - metrics.descent = javaMetrics.getDescent(); - metrics.bottom = javaMetrics.getMaxDescent(); - metrics.leading = javaMetrics.getLeading(); - } - - return javaMetrics.getHeight(); - } - - return 0; - } - - /** - * Reimplemented to return Paint.FontMetrics instead of _Original_Paint.FontMetrics - */ - public FontMetrics getFontMetrics() { - FontMetrics fm = new FontMetrics(); - getFontMetrics(fm); - return fm; - } - - /** - * Reimplemented to return Paint.FontMetricsInt instead of _Original_Paint.FontMetricsInt - */ - public FontMetricsInt getFontMetricsInt() { - FontMetricsInt fm = new FontMetricsInt(); - getFontMetricsInt(fm); - return fm; - } - - - - @Override - public float getFontMetrics(_Original_Paint.FontMetrics metrics) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - @Override - public int getFontMetricsInt(_Original_Paint.FontMetricsInt metrics) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - @Override - public Typeface setTypeface(Typeface typeface) { - if (typeface != null) { - mTypeface = typeface; - } else { - mTypeface = Typeface.DEFAULT; - } - - updateFontObject(); - - return typeface; - } - - @Override - public Typeface getTypeface() { - return super.getTypeface(); - } - - @Override - public int getColor() { - return mColor; - } - - @Override - public void setColor(int color) { - mColor = color; - } - - @Override - public void setARGB(int a, int r, int g, int b) { - super.setARGB(a, r, g, b); - } - - @Override - public void setAlpha(int alpha) { - mColor = (alpha << 24) | (mColor & 0x00FFFFFF); - } - - @Override - public int getAlpha() { - return mColor >>> 24; - } - - /** - * Set or clear the shader object. - * <p /> - * Pass null to clear any previous shader. - * As a convenience, the parameter passed is also returned. - * - * @param shader May be null. the new shader to be installed in the paint - * @return shader - */ - @Override - public Shader setShader(Shader shader) { - return mShader = shader; - } - - @Override - public Shader getShader() { - return super.getShader(); - } - - /** - * Set or clear the paint's colorfilter, returning the parameter. - * - * @param filter May be null. The new filter to be installed in the paint - * @return filter - */ - @Override - public ColorFilter setColorFilter(ColorFilter filter) { - mColorFilter = filter; - return filter; - } - - @Override - public ColorFilter getColorFilter() { - return super.getColorFilter(); - } - - /** - * Set or clear the xfermode object. - * <p /> - * Pass null to clear any previous xfermode. - * As a convenience, the parameter passed is also returned. - * - * @param xfermode May be null. The xfermode to be installed in the paint - * @return xfermode - */ - @Override - public Xfermode setXfermode(Xfermode xfermode) { - return mXfermode = xfermode; - } - - @Override - public Xfermode getXfermode() { - return super.getXfermode(); - } - - @Override - public Rasterizer setRasterizer(Rasterizer rasterizer) { - mRasterizer = rasterizer; - return rasterizer; - } - - @Override - public Rasterizer getRasterizer() { - return super.getRasterizer(); - } - - @Override - public void setShadowLayer(float radius, float dx, float dy, int color) { - // TODO Auto-generated method stub - } - - @Override - public void clearShadowLayer() { - super.clearShadowLayer(); - } - - public void setTextAlign(Align align) { - mAlign = align; - } - - @Override - public void setTextAlign(android.graphics._Original_Paint.Align align) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public Align getTextAlign() { - return mAlign; - } - - public void setStyle(Style style) { - mStyle = style; - } - - @Override - public void setStyle(android.graphics._Original_Paint.Style style) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public Style getStyle() { - return mStyle; - } - - @Override - public void setDither(boolean dither) { - mFlags |= dither ? DITHER_FLAG : ~DITHER_FLAG; - } - - @Override - public void setAntiAlias(boolean aa) { - mFlags |= aa ? ANTI_ALIAS_FLAG : ~ANTI_ALIAS_FLAG; - } - - @Override - public void setFakeBoldText(boolean flag) { - mFlags |= flag ? FAKE_BOLD_TEXT_FLAG : ~FAKE_BOLD_TEXT_FLAG; - } - - @Override - public void setLinearText(boolean flag) { - mFlags |= flag ? LINEAR_TEXT_FLAG : ~LINEAR_TEXT_FLAG; - } - - @Override - public void setSubpixelText(boolean flag) { - mFlags |= flag ? SUBPIXEL_TEXT_FLAG : ~SUBPIXEL_TEXT_FLAG; - } - - @Override - public void setUnderlineText(boolean flag) { - mFlags |= flag ? UNDERLINE_TEXT_FLAG : ~UNDERLINE_TEXT_FLAG; - } - - @Override - public void setStrikeThruText(boolean flag) { - mFlags |= flag ? STRIKE_THRU_TEXT_FLAG : ~STRIKE_THRU_TEXT_FLAG; - } - - @Override - public void setFilterBitmap(boolean flag) { - mFlags |= flag ? FILTER_BITMAP_FLAG : ~FILTER_BITMAP_FLAG; - } - - @Override - public float getStrokeWidth() { - return mStrokeWidth; - } - - @Override - public void setStrokeWidth(float width) { - mStrokeWidth = width; - } - - @Override - public float getStrokeMiter() { - return mStrokeMiter; - } - - @Override - public void setStrokeMiter(float miter) { - mStrokeMiter = miter; - } - - @Override - public void setStrokeCap(android.graphics._Original_Paint.Cap cap) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public void setStrokeCap(Cap cap) { - mCap = cap; - } - - public Cap getStrokeCap() { - return mCap; - } - - @Override - public void setStrokeJoin(android.graphics._Original_Paint.Join join) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public void setStrokeJoin(Join join) { - mJoin = join; - } - - public Join getStrokeJoin() { - return mJoin; - } - - @Override - public boolean getFillPath(Path src, Path dst) { - return super.getFillPath(src, dst); - } - - @Override - public PathEffect setPathEffect(PathEffect effect) { - mPathEffect = effect; - return effect; - } - - @Override - public PathEffect getPathEffect() { - return super.getPathEffect(); - } - - @Override - public MaskFilter setMaskFilter(MaskFilter maskfilter) { - mMaskFilter = maskfilter; - return maskfilter; - } - - @Override - public MaskFilter getMaskFilter() { - return super.getMaskFilter(); - } - - /** - * Return the paint's text size. - * - * @return the paint's text size. - */ - @Override - public float getTextSize() { - return mTextSize; - } - - /** - * Set the paint's text size. This value must be > 0 - * - * @param textSize set the paint's text size. - */ - @Override - public void setTextSize(float textSize) { - mTextSize = textSize; - - updateFontObject(); - } - - /** - * Return the paint's horizontal scale factor for text. The default value - * is 1.0. - * - * @return the paint's scale factor in X for drawing/measuring text - */ - @Override - public float getTextScaleX() { - return mScaleX; - } - - /** - * Set the paint's horizontal scale factor for text. The default value - * is 1.0. Values > 1.0 will stretch the text wider. Values < 1.0 will - * stretch the text narrower. - * - * @param scaleX set the paint's scale in X for drawing/measuring text. - */ - @Override - public void setTextScaleX(float scaleX) { - mScaleX = scaleX; - - updateFontObject(); - } - - /** - * Return the paint's horizontal skew factor for text. The default value - * is 0. - * - * @return the paint's skew factor in X for drawing text. - */ - @Override - public float getTextSkewX() { - return mSkewX; - } - - /** - * Set the paint's horizontal skew factor for text. The default value - * is 0. For approximating oblique text, use values around -0.25. - * - * @param skewX set the paint's skew factor in X for drawing text. - */ - @Override - public void setTextSkewX(float skewX) { - mSkewX = skewX; - - updateFontObject(); - } - - @Override - public float getFontSpacing() { - return super.getFontSpacing(); - } - - /** - * Return the distance above (negative) the baseline (ascent) based on the - * current typeface and text size. - * - * @return the distance above (negative) the baseline (ascent) based on the - * current typeface and text size. - */ - @Override - public float ascent() { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - // Android expects negative ascent so we invert the value from Java. - return - javaMetrics.getAscent(); - } - - return 0; - } - - /** - * Return the distance below (positive) the baseline (descent) based on the - * current typeface and text size. - * - * @return the distance below (positive) the baseline (descent) based on - * the current typeface and text size. - */ - @Override - public float descent() { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - return javaMetrics.getDescent(); - } - - return 0; - } - - /** - * Return the width of the text. - * - * @param text The text to measure - * @param index The index of the first character to start measuring - * @param count THe number of characters to measure, beginning with start - * @return The width of the text - */ - @Override - public float measureText(char[] text, int index, int count) { - // WARNING: the logic in this method is similar to Canvas.drawText. - // Any change to this method should be reflected in Canvas.drawText - if (mFonts.size() > 0) { - FontInfo mainFont = mFonts.get(0); - int i = index; - int lastIndex = index + count; - float total = 0f; - while (i < lastIndex) { - // always start with the main font. - int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); - if (upTo == -1) { - // shortcut to exit - return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i); - } else if (upTo > 0) { - total += mainFont.mMetrics.charsWidth(text, i, upTo - i); - i = upTo; - // don't call continue at this point. Since it is certain the main font - // cannot display the font a index upTo (now ==i), we move on to the - // fallback fonts directly. - } - - // no char supported, attempt to read the next char(s) with the - // fallback font. In this case we only test the first character - // and then go back to test with the main font. - // Special test for 2-char characters. - boolean foundFont = false; - for (int f = 1 ; f < mFonts.size() ; f++) { - FontInfo fontInfo = mFonts.get(f); - - // need to check that the font can display the character. We test - // differently if the char is a high surrogate. - int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; - upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); - if (upTo == -1) { - total += fontInfo.mMetrics.charsWidth(text, i, charCount); - i += charCount; - foundFont = true; - break; - - } - } - - // in case no font can display the char, measure it with the main font. - if (foundFont == false) { - int size = Character.isHighSurrogate(text[i]) ? 2 : 1; - total += mainFont.mMetrics.charsWidth(text, i, size); - i += size; - } - } - } - - return 0; - } - - /** - * Return the width of the text. - * - * @param text The text to measure - * @param start The index of the first character to start measuring - * @param end 1 beyond the index of the last character to measure - * @return The width of the text - */ - @Override - public float measureText(String text, int start, int end) { - return measureText(text.toCharArray(), start, end - start); - } - - /** - * Return the width of the text. - * - * @param text The text to measure - * @return The width of the text - */ - @Override - public float measureText(String text) { - return measureText(text.toCharArray(), 0, text.length()); - } - - /* - * re-implement to call SpannableStringBuilder.measureText with a Paint object - * instead of an _Original_Paint - */ - @Override - public float measureText(CharSequence text, int start, int end) { - if (text instanceof String) { - return measureText((String)text, start, end); - } - if (text instanceof SpannedString || - text instanceof SpannableString) { - return measureText(text.toString(), start, end); - } - if (text instanceof SpannableStringBuilder) { - return ((SpannableStringBuilder)text).measureText(start, end, this); - } - - char[] buf = TemporaryBuffer.obtain(end - start); - TextUtils.getChars(text, start, end, buf, 0); - float result = measureText(buf, 0, end - start); - TemporaryBuffer.recycle(buf); - return result; - } - - /** - * Measure the text, stopping early if the measured width exceeds maxWidth. - * Return the number of chars that were measured, and if measuredWidth is - * not null, return in it the actual width measured. - * - * @param text The text to measure - * @param index The offset into text to begin measuring at - * @param count The number of maximum number of entries to measure. If count - * is negative, then the characters before index are measured - * in reverse order. This allows for measuring the end of - * string. - * @param maxWidth The maximum width to accumulate. - * @param measuredWidth Optional. If not null, returns the actual width - * measured. - * @return The number of chars that were measured. Will always be <= - * abs(count). - */ - @Override - public int breakText(char[] text, int index, int count, - float maxWidth, float[] measuredWidth) { - int inc = count > 0 ? 1 : -1; - - int measureIndex = 0; - float measureAcc = 0; - for (int i = index ; i != index + count ; i += inc, measureIndex++) { - int start, end; - if (i < index) { - start = i; - end = index; - } else { - start = index; - end = i; - } - - // measure from start to end - float res = measureText(text, start, end - start + 1); - - if (measuredWidth != null) { - measuredWidth[measureIndex] = res; - } - - measureAcc += res; - if (res > maxWidth) { - // we should not return this char index, but since it's 0-based and we need - // to return a count, we simply return measureIndex; - return measureIndex; - } - - } - - return measureIndex; - } - - /** - * Measure the text, stopping early if the measured width exceeds maxWidth. - * Return the number of chars that were measured, and if measuredWidth is - * not null, return in it the actual width measured. - * - * @param text The text to measure - * @param measureForwards If true, measure forwards, starting at index. - * Otherwise, measure backwards, starting with the - * last character in the string. - * @param maxWidth The maximum width to accumulate. - * @param measuredWidth Optional. If not null, returns the actual width - * measured. - * @return The number of chars that were measured. Will always be <= - * abs(count). - */ - @Override - public int breakText(String text, boolean measureForwards, - float maxWidth, float[] measuredWidth) { - return breakText(text, - 0 /* start */, text.length() /* end */, - measureForwards, maxWidth, measuredWidth); - } - - /** - * Measure the text, stopping early if the measured width exceeds maxWidth. - * Return the number of chars that were measured, and if measuredWidth is - * not null, return in it the actual width measured. - * - * @param text The text to measure - * @param start The offset into text to begin measuring at - * @param end The end of the text slice to measure. - * @param measureForwards If true, measure forwards, starting at start. - * Otherwise, measure backwards, starting with end. - * @param maxWidth The maximum width to accumulate. - * @param measuredWidth Optional. If not null, returns the actual width - * measured. - * @return The number of chars that were measured. Will always be <= - * abs(end - start). - */ - @Override - public int breakText(CharSequence text, int start, int end, boolean measureForwards, - float maxWidth, float[] measuredWidth) { - char[] buf = new char[end - start]; - int result; - - TextUtils.getChars(text, start, end, buf, 0); - - if (measureForwards) { - result = breakText(buf, 0, end - start, maxWidth, measuredWidth); - } else { - result = breakText(buf, 0, -(end - start), maxWidth, measuredWidth); - } - - return result; - } - - /** - * Return the advance widths for the characters in the string. - * - * @param text The text to measure - * @param index The index of the first char to to measure - * @param count The number of chars starting with index to measure - * @param widths array to receive the advance widths of the characters. - * Must be at least a large as count. - * @return the actual number of widths returned. - */ - @Override - public int getTextWidths(char[] text, int index, int count, - float[] widths) { - if (mFonts.size() > 0) { - if ((index | count) < 0 || index + count > text.length - || count > widths.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - // FIXME: handle multi-char characters. - // Need to figure out if the lengths of the width array takes into account - // multi-char characters. - for (int i = 0; i < count; i++) { - char c = text[i + index]; - boolean found = false; - for (FontInfo info : mFonts) { - if (info.mFont.canDisplay(c)) { - widths[i] = info.mMetrics.charWidth(c); - found = true; - break; - } - } - - if (found == false) { - // we stop there. - return i; - } - } - - return count; - } - - return 0; - } - - /** - * Return the advance widths for the characters in the string. - * - * @param text The text to measure - * @param start The index of the first char to to measure - * @param end The end of the text slice to measure - * @param widths array to receive the advance widths of the characters. - * Must be at least a large as the text. - * @return the number of unichars in the specified text. - */ - @Override - public int getTextWidths(String text, int start, int end, float[] widths) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - if (end - start > widths.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - return getTextWidths(text.toCharArray(), start, end - start, widths); - } - - /* - * re-implement to call SpannableStringBuilder.getTextWidths with a Paint object - * instead of an _Original_Paint - */ - @Override - public int getTextWidths(CharSequence text, int start, int end, float[] widths) { - if (text instanceof String) { - return getTextWidths((String)text, start, end, widths); - } - if (text instanceof SpannedString || text instanceof SpannableString) { - return getTextWidths(text.toString(), start, end, widths); - } - if (text instanceof SpannableStringBuilder) { - return ((SpannableStringBuilder)text).getTextWidths(start, end, widths, this); - } - - char[] buf = TemporaryBuffer.obtain(end - start); - TextUtils.getChars(text, start, end, buf, 0); - int result = getTextWidths(buf, 0, end - start, widths); - TemporaryBuffer.recycle(buf); - return result; - } - - @Override - public int getTextWidths(String text, float[] widths) { - return super.getTextWidths(text, widths); - } - - /** - * Return the path (outline) for the specified text. - * Note: just like Canvas.drawText, this will respect the Align setting in - * the paint. - * - * @param text The text to retrieve the path from - * @param index The index of the first character in text - * @param count The number of characterss starting with index - * @param x The x coordinate of the text's origin - * @param y The y coordinate of the text's origin - * @param path The path to receive the data describing the text. Must - * be allocated by the caller. - */ - @Override - public void getTextPath(char[] text, int index, int count, - float x, float y, Path path) { - - // TODO this is the ORIGINAL implementation. REPLACE AS NEEDED OR REMOVE - - if ((index | count) < 0 || index + count > text.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - // TODO native_getTextPath(mNativePaint, text, index, count, x, y, path.ni()); - - throw new UnsupportedOperationException("IMPLEMENT AS NEEDED"); - } - - /** - * Return the path (outline) for the specified text. - * Note: just like Canvas.drawText, this will respect the Align setting - * in the paint. - * - * @param text The text to retrieve the path from - * @param start The first character in the text - * @param end 1 past the last charcter in the text - * @param x The x coordinate of the text's origin - * @param y The y coordinate of the text's origin - * @param path The path to receive the data describing the text. Must - * be allocated by the caller. - */ - @Override - public void getTextPath(String text, int start, int end, - float x, float y, Path path) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - - getTextPath(text.toCharArray(), start, end - start, x, y, path); - } - - /** - * Return in bounds (allocated by the caller) the smallest rectangle that - * encloses all of the characters, with an implied origin at (0,0). - * - * @param text String to measure and return its bounds - * @param start Index of the first char in the string to measure - * @param end 1 past the last char in the string measure - * @param bounds Returns the unioned bounds of all the text. Must be - * allocated by the caller. - */ - @Override - public void getTextBounds(String text, int start, int end, Rect bounds) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - if (bounds == null) { - throw new NullPointerException("need bounds Rect"); - } - - getTextBounds(text.toCharArray(), start, end - start, bounds); - } - - /** - * Return in bounds (allocated by the caller) the smallest rectangle that - * encloses all of the characters, with an implied origin at (0,0). - * - * @param text Array of chars to measure and return their unioned bounds - * @param index Index of the first char in the array to measure - * @param count The number of chars, beginning at index, to measure - * @param bounds Returns the unioned bounds of all the text. Must be - * allocated by the caller. - */ - @Override - public void getTextBounds(char[] text, int index, int count, Rect bounds) { - // FIXME - if (mFonts.size() > 0) { - if ((index | count) < 0 || index + count > text.length) { - throw new ArrayIndexOutOfBoundsException(); - } - if (bounds == null) { - throw new NullPointerException("need bounds Rect"); - } - - FontInfo mainInfo = mFonts.get(0); - - Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, mFontContext); - bounds.set(0, 0, (int)rect.getWidth(), (int)rect.getHeight()); - } - } - - public static void finalizer(int foo) { - // pass - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java new file mode 100644 index 0000000..71d346a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.PaintFlagsDrawFilter + * + * Through the layoutlib_create tool, the original native methods of PaintFlagsDrawFilter 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 PaintFlagsDrawFilter class. + * + * Because this extends {@link DrawFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the DrawFilter classes will be added to the manager owned by + * {@link DrawFilter_Delegate}. + * + * @see DrawFilter_Delegate + * + */ +public class PaintFlagsDrawFilter_Delegate extends DrawFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Paint Flags Draw Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor(int clearBits, int setBits) { + PaintFlagsDrawFilter_Delegate newDelegate = new PaintFlagsDrawFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java new file mode 100644 index 0000000..373f482 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -0,0 +1,1240 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Paint.FontMetrics; +import android.graphics.Paint.FontMetricsInt; +import android.text.TextUtils; + +import java.awt.BasicStroke; +import java.awt.Font; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.Toolkit; +import java.awt.font.FontRenderContext; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Delegate implementing the native methods of android.graphics.Paint + * + * Through the layoutlib_create tool, the original native methods of Paint 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 Paint class. + * + * @see DelegateManager + * + */ +public class Paint_Delegate { + + /** + * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. + */ + /*package*/ static final class FontInfo { + Font mFont; + java.awt.FontMetrics mMetrics; + } + + // ---- delegate manager ---- + private static final DelegateManager<Paint_Delegate> sManager = + new DelegateManager<Paint_Delegate>(Paint_Delegate.class); + + // ---- delegate helper data ---- + private List<FontInfo> mFonts; + private final FontRenderContext mFontContext = new FontRenderContext( + new AffineTransform(), true, true); + + // ---- delegate data ---- + private int mFlags; + private int mColor; + private int mStyle; + private int mCap; + private int mJoin; + private int mTextAlign; + private Typeface_Delegate mTypeface; + private float mStrokeWidth; + private float mStrokeMiter; + private float mTextSize; + private float mTextScaleX; + private float mTextSkewX; + + private Xfermode_Delegate mXfermode; + private ColorFilter_Delegate mColorFilter; + private Shader_Delegate mShader; + private PathEffect_Delegate mPathEffect; + private MaskFilter_Delegate mMaskFilter; + private Rasterizer_Delegate mRasterizer; + + + // ---- Public Helper methods ---- + + public static Paint_Delegate getDelegate(int native_paint) { + return sManager.getDelegate(native_paint); + } + + /** + * Returns the list of {@link Font} objects. The first item is the main font, the rest + * are fall backs for characters not present in the main font. + */ + public List<FontInfo> getFonts() { + return mFonts; + } + + public boolean isAntiAliased() { + return (mFlags & Paint.ANTI_ALIAS_FLAG) != 0; + } + + public boolean isFilterBitmap() { + return (mFlags & Paint.FILTER_BITMAP_FLAG) != 0; + } + + public int getStyle() { + return mStyle; + } + + public int getColor() { + return mColor; + } + + public int getAlpha() { + return mColor >>> 24; + } + + public void setAlpha(int alpha) { + mColor = (alpha << 24) | (mColor & 0x00FFFFFF); + } + + public int getTextAlign() { + return mTextAlign; + } + + public float getStrokeWidth() { + return mStrokeWidth; + } + + /** + * returns the value of stroke miter needed by the java api. + */ + public float getJavaStrokeMiter() { + float miter = mStrokeMiter * mStrokeWidth; + if (miter < 1.f) { + miter = 1.f; + } + return miter; + } + + public int getJavaCap() { + switch (Paint.sCapArray[mCap]) { + case BUTT: + return BasicStroke.CAP_BUTT; + case ROUND: + return BasicStroke.CAP_ROUND; + default: + case SQUARE: + return BasicStroke.CAP_SQUARE; + } + } + + public int getJavaJoin() { + switch (Paint.sJoinArray[mJoin]) { + default: + case MITER: + return BasicStroke.JOIN_MITER; + case ROUND: + return BasicStroke.JOIN_ROUND; + case BEVEL: + return BasicStroke.JOIN_BEVEL; + } + } + + public Stroke getJavaStroke() { + if (mPathEffect != null) { + if (mPathEffect.isSupported()) { + Stroke stroke = mPathEffect.getStroke(this); + assert stroke != null; + if (stroke != null) { + return stroke; + } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_PATHEFFECT, + mPathEffect.getSupportMessage(), + null, null /*data*/); + } + } + + // if no custom stroke as been set, set the default one. + return new BasicStroke( + getStrokeWidth(), + getJavaCap(), + getJavaJoin(), + getJavaStrokeMiter()); + } + + /** + * Returns the {@link Xfermode} delegate or null if none have been set + * + * @return the delegate or null. + */ + public Xfermode_Delegate getXfermode() { + return mXfermode; + } + + /** + * Returns the {@link ColorFilter} delegate or null if none have been set + * + * @return the delegate or null. + */ + public ColorFilter_Delegate getColorFilter() { + return mColorFilter; + } + + /** + * Returns the {@link Shader} delegate or null if none have been set + * + * @return the delegate or null. + */ + public Shader_Delegate getShader() { + return mShader; + } + + /** + * Returns the {@link MaskFilter} delegate or null if none have been set + * + * @return the delegate or null. + */ + public MaskFilter_Delegate getMaskFilter() { + return mMaskFilter; + } + + /** + * Returns the {@link Rasterizer} delegate or null if none have been set + * + * @return the delegate or null. + */ + public Rasterizer_Delegate getRasterizer() { + return mRasterizer; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int getFlags(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.mFlags; + } + + @LayoutlibDelegate + /*package*/ static void setFlags(Paint thisPaint, int flags) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mFlags = flags; + } + + @LayoutlibDelegate + /*package*/ static void setFilterBitmap(Paint thisPaint, boolean filter) { + setFlag(thisPaint, Paint.FILTER_BITMAP_FLAG, filter); + } + + @LayoutlibDelegate + /*package*/ static void setAntiAlias(Paint thisPaint, boolean aa) { + setFlag(thisPaint, Paint.ANTI_ALIAS_FLAG, aa); + } + + @LayoutlibDelegate + /*package*/ static void setSubpixelText(Paint thisPaint, boolean subpixelText) { + setFlag(thisPaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText); + } + + @LayoutlibDelegate + /*package*/ static void setUnderlineText(Paint thisPaint, boolean underlineText) { + setFlag(thisPaint, Paint.UNDERLINE_TEXT_FLAG, underlineText); + } + + @LayoutlibDelegate + /*package*/ static void setStrikeThruText(Paint thisPaint, boolean strikeThruText) { + setFlag(thisPaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText); + } + + @LayoutlibDelegate + /*package*/ static void setFakeBoldText(Paint thisPaint, boolean fakeBoldText) { + setFlag(thisPaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText); + } + + @LayoutlibDelegate + /*package*/ static void setDither(Paint thisPaint, boolean dither) { + setFlag(thisPaint, Paint.DITHER_FLAG, dither); + } + + @LayoutlibDelegate + /*package*/ static void setLinearText(Paint thisPaint, boolean linearText) { + setFlag(thisPaint, Paint.LINEAR_TEXT_FLAG, linearText); + } + + @LayoutlibDelegate + /*package*/ static int getColor(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.mColor; + } + + @LayoutlibDelegate + /*package*/ static void setColor(Paint thisPaint, int color) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mColor = color; + } + + @LayoutlibDelegate + /*package*/ static int getAlpha(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.getAlpha(); + } + + @LayoutlibDelegate + /*package*/ static void setAlpha(Paint thisPaint, int a) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.setAlpha(a); + } + + @LayoutlibDelegate + /*package*/ static float getStrokeWidth(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mStrokeWidth; + } + + @LayoutlibDelegate + /*package*/ static void setStrokeWidth(Paint thisPaint, float width) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mStrokeWidth = width; + } + + @LayoutlibDelegate + /*package*/ static float getStrokeMiter(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mStrokeMiter; + } + + @LayoutlibDelegate + /*package*/ static void setStrokeMiter(Paint thisPaint, float miter) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mStrokeMiter = miter; + } + + @LayoutlibDelegate + /*package*/ static void nSetShadowLayer(Paint thisPaint, float radius, float dx, float dy, + int color) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.setShadowLayer is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static float getTextSize(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mTextSize; + } + + @LayoutlibDelegate + /*package*/ static void setTextSize(Paint thisPaint, float textSize) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mTextSize = textSize; + delegate.updateFontObject(); + } + + @LayoutlibDelegate + /*package*/ static float getTextScaleX(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mTextScaleX; + } + + @LayoutlibDelegate + /*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mTextScaleX = scaleX; + delegate.updateFontObject(); + } + + @LayoutlibDelegate + /*package*/ static float getTextSkewX(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mTextSkewX; + } + + @LayoutlibDelegate + /*package*/ static void setTextSkewX(Paint thisPaint, float skewX) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mTextSkewX = skewX; + delegate.updateFontObject(); + } + + @LayoutlibDelegate + /*package*/ static float ascent(Paint thisPaint) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics; + // Android expects negative ascent so we invert the value from Java. + return - javaMetrics.getAscent(); + } + + return 0; + } + + @LayoutlibDelegate + /*package*/ static float descent(Paint thisPaint) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics; + return javaMetrics.getDescent(); + } + + return 0; + + } + + @LayoutlibDelegate + /*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.getFontMetrics(metrics); + } + + @LayoutlibDelegate + /*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics; + if (fmi != null) { + // Android expects negative ascent so we invert the value from Java. + fmi.top = - javaMetrics.getMaxAscent(); + fmi.ascent = - javaMetrics.getAscent(); + fmi.descent = javaMetrics.getDescent(); + fmi.bottom = javaMetrics.getMaxDescent(); + fmi.leading = javaMetrics.getLeading(); + } + + return javaMetrics.getHeight(); + } + + return 0; + } + + @LayoutlibDelegate + /*package*/ static float native_measureText(Paint thisPaint, char[] text, int index, + int count) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.measureText(text, index, count); + } + + @LayoutlibDelegate + /*package*/ static float native_measureText(Paint thisPaint, String text, int start, int end) { + return native_measureText(thisPaint, text.toCharArray(), start, end - start); + } + + @LayoutlibDelegate + /*package*/ static float native_measureText(Paint thisPaint, String text) { + return native_measureText(thisPaint, text.toCharArray(), 0, text.length()); + } + + @LayoutlibDelegate + /*package*/ static int native_breakText(Paint thisPaint, char[] text, int index, int count, + float maxWidth, float[] measuredWidth) { + + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + int inc = count > 0 ? 1 : -1; + + int measureIndex = 0; + float measureAcc = 0; + for (int i = index; i != index + count; i += inc, measureIndex++) { + int start, end; + if (i < index) { + start = i; + end = index; + } else { + start = index; + end = i; + } + + // measure from start to end + float res = delegate.measureText(text, start, end - start + 1); + + if (measuredWidth != null) { + measuredWidth[measureIndex] = res; + } + + measureAcc += res; + if (res > maxWidth) { + // we should not return this char index, but since it's 0-based + // and we need to return a count, we simply return measureIndex; + return measureIndex; + } + + } + + return measureIndex; + } + + @LayoutlibDelegate + /*package*/ static int native_breakText(Paint thisPaint, String text, boolean measureForwards, + float maxWidth, float[] measuredWidth) { + return native_breakText(thisPaint, text.toCharArray(), 0, text.length(), maxWidth, + measuredWidth); + } + + @LayoutlibDelegate + /*package*/ static int native_init() { + Paint_Delegate newDelegate = new Paint_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int native_initWithPaint(int paint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(paint); + if (delegate == null) { + return 0; + } + + Paint_Delegate newDelegate = new Paint_Delegate(delegate); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_reset(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.reset(); + } + + @LayoutlibDelegate + /*package*/ static void native_set(int native_dst, int native_src) { + // get the delegate from the native int. + Paint_Delegate delegate_dst = sManager.getDelegate(native_dst); + if (delegate_dst == null) { + return; + } + + // get the delegate from the native int. + Paint_Delegate delegate_src = sManager.getDelegate(native_src); + if (delegate_src == null) { + return; + } + + delegate_dst.set(delegate_src); + } + + @LayoutlibDelegate + /*package*/ static int native_getStyle(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mStyle; + } + + @LayoutlibDelegate + /*package*/ static void native_setStyle(int native_object, int style) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mStyle = style; + } + + @LayoutlibDelegate + /*package*/ static int native_getStrokeCap(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mCap; + } + + @LayoutlibDelegate + /*package*/ static void native_setStrokeCap(int native_object, int cap) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mCap = cap; + } + + @LayoutlibDelegate + /*package*/ static int native_getStrokeJoin(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mJoin; + } + + @LayoutlibDelegate + /*package*/ static void native_setStrokeJoin(int native_object, int join) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mJoin = join; + } + + @LayoutlibDelegate + /*package*/ static boolean native_getFillPath(int native_object, int src, int dst) { + Paint_Delegate paint = sManager.getDelegate(native_object); + if (paint == null) { + return false; + } + + Path_Delegate srcPath = Path_Delegate.getDelegate(src); + if (srcPath == null) { + return true; + } + + Path_Delegate dstPath = Path_Delegate.getDelegate(dst); + if (dstPath == null) { + return true; + } + + Stroke stroke = paint.getJavaStroke(); + Shape strokeShape = stroke.createStrokedShape(srcPath.getJavaShape()); + + dstPath.setJavaShape(strokeShape); + + // FIXME figure out the return value? + return true; + } + + @LayoutlibDelegate + /*package*/ static int native_setShader(int native_object, int shader) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return shader; + } + + delegate.mShader = Shader_Delegate.getDelegate(shader); + + return shader; + } + + @LayoutlibDelegate + /*package*/ static int native_setColorFilter(int native_object, int filter) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return filter; + } + + delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);; + + // since none of those are supported, display a fidelity warning right away + if (delegate.mColorFilter != null && delegate.mColorFilter.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_COLORFILTER, + delegate.mColorFilter.getSupportMessage(), null, null /*data*/); + } + + return filter; + } + + @LayoutlibDelegate + /*package*/ static int native_setXfermode(int native_object, int xfermode) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return xfermode; + } + + delegate.mXfermode = Xfermode_Delegate.getDelegate(xfermode); + + return xfermode; + } + + @LayoutlibDelegate + /*package*/ static int native_setPathEffect(int native_object, int effect) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return effect; + } + + delegate.mPathEffect = PathEffect_Delegate.getDelegate(effect); + + return effect; + } + + @LayoutlibDelegate + /*package*/ static int native_setMaskFilter(int native_object, int maskfilter) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return maskfilter; + } + + delegate.mMaskFilter = MaskFilter_Delegate.getDelegate(maskfilter); + + // since none of those are supported, display a fidelity warning right away + if (delegate.mMaskFilter != null && delegate.mMaskFilter.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, + delegate.mMaskFilter.getSupportMessage(), null, null /*data*/); + } + + return maskfilter; + } + + @LayoutlibDelegate + /*package*/ static int native_setTypeface(int native_object, int typeface) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + delegate.mTypeface = Typeface_Delegate.getDelegate(typeface); + delegate.updateFontObject(); + return typeface; + } + + @LayoutlibDelegate + /*package*/ static int native_setRasterizer(int native_object, int rasterizer) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return rasterizer; + } + + delegate.mRasterizer = Rasterizer_Delegate.getDelegate(rasterizer); + + // since none of those are supported, display a fidelity warning right away + if (delegate.mRasterizer != null && delegate.mRasterizer.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_RASTERIZER, + delegate.mRasterizer.getSupportMessage(), null, null /*data*/); + } + + return rasterizer; + } + + @LayoutlibDelegate + /*package*/ static int native_getTextAlign(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mTextAlign; + } + + @LayoutlibDelegate + /*package*/ static void native_setTextAlign(int native_object, int align) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mTextAlign = align; + } + + @LayoutlibDelegate + /*package*/ static float native_getFontMetrics(int native_paint, FontMetrics metrics) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_paint); + if (delegate == null) { + return 0.f; + } + + return delegate.getFontMetrics(metrics); + } + + @LayoutlibDelegate + /*package*/ static int native_getTextWidths(int native_object, char[] text, int index, + int count, float[] widths) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + // FIXME: handle multi-char characters (see measureText) + float totalAdvance = 0; + for (int i = 0; i < count; i++) { + char c = text[i + index]; + boolean found = false; + for (FontInfo info : delegate.mFonts) { + if (info.mFont.canDisplay(c)) { + float adv = info.mMetrics.charWidth(c); + totalAdvance += adv; + if (widths != null) { + widths[i] = adv; + } + + found = true; + break; + } + } + + if (found == false) { + // no advance for this char. + if (widths != null) { + widths[i] = 0.f; + } + } + } + + return (int) totalAdvance; + } + + return 0; + } + + @LayoutlibDelegate + /*package*/ static int native_getTextWidths(int native_object, String text, int start, + int end, float[] widths) { + return native_getTextWidths(native_object, text.toCharArray(), start, end - start, widths); + } + + @LayoutlibDelegate + /*package*/ static float native_getTextRunAdvances(int native_object, + char[] text, int index, int count, int contextIndex, int contextCount, + int flags, float[] advances, int advancesIndex) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0.f; + } + + if (delegate.mFonts.size() > 0) { + // FIXME: handle multi-char characters (see measureText) + float totalAdvance = 0; + for (int i = 0; i < count; i++) { + char c = text[i + index]; + boolean found = false; + for (FontInfo info : delegate.mFonts) { + if (info.mFont.canDisplay(c)) { + float adv = info.mMetrics.charWidth(c); + totalAdvance += adv; + if (advances != null) { + advances[i] = adv; + } + + found = true; + break; + } + } + + if (found == false) { + // no advance for this char. + if (advances != null) { + advances[i] = 0.f; + } + } + } + + return totalAdvance; + } + + return 0; + + } + + @LayoutlibDelegate + /*package*/ static float native_getTextRunAdvances(int native_object, + String text, int start, int end, int contextStart, int contextEnd, + int flags, float[] advances, int advancesIndex) { + // FIXME: support contextStart, contextEnd and direction flag + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + return native_getTextRunAdvances(native_object, buffer, 0, count, contextStart, + contextEnd - contextStart, flags, advances, advancesIndex); + } + + @LayoutlibDelegate + /*package*/ static int native_getTextRunCursor(Paint thisPaint, int native_object, char[] text, + int contextStart, int contextLength, int flags, int offset, int cursorOpt) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.getTextRunCursor is not supported.", null, null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static int native_getTextRunCursor(Paint thisPaint, int native_object, String text, + int contextStart, int contextEnd, int flags, int offset, int cursorOpt) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.getTextRunCursor is not supported.", null, null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static void native_getTextPath(int native_object, int bidiFlags, + char[] text, int index, int count, float x, float y, int path) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.getTextPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_getTextPath(int native_object, int bidiFlags, + String text, int start, int end, float x, float y, int path) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.getTextPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeGetStringBounds(int nativePaint, String text, int start, + int end, Rect bounds) { + nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start, bounds); + } + + @LayoutlibDelegate + /*package*/ static void nativeGetCharArrayBounds(int nativePaint, char[] text, int index, + int count, Rect bounds) { + + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(nativePaint); + if (delegate == null) { + return; + } + + // FIXME should test if the main font can display all those characters. + // See MeasureText + if (delegate.mFonts.size() > 0) { + FontInfo mainInfo = delegate.mFonts.get(0); + + Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, + delegate.mFontContext); + bounds.set(0, 0, (int) rect.getWidth(), (int) rect.getHeight()); + } + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int nativePaint) { + sManager.removeJavaReferenceFor(nativePaint); + } + + // ---- Private delegate/helper methods ---- + + /*package*/ Paint_Delegate() { + reset(); + } + + private Paint_Delegate(Paint_Delegate paint) { + set(paint); + } + + private void set(Paint_Delegate paint) { + mFlags = paint.mFlags; + mColor = paint.mColor; + mStyle = paint.mStyle; + mCap = paint.mCap; + mJoin = paint.mJoin; + mTextAlign = paint.mTextAlign; + mTypeface = paint.mTypeface; + mStrokeWidth = paint.mStrokeWidth; + mStrokeMiter = paint.mStrokeMiter; + mTextSize = paint.mTextSize; + mTextScaleX = paint.mTextScaleX; + mTextSkewX = paint.mTextSkewX; + mXfermode = paint.mXfermode; + mColorFilter = paint.mColorFilter; + mShader = paint.mShader; + mPathEffect = paint.mPathEffect; + mMaskFilter = paint.mMaskFilter; + mRasterizer = paint.mRasterizer; + updateFontObject(); + } + + private void reset() { + mFlags = Paint.DEFAULT_PAINT_FLAGS; + mColor = 0xFF000000; + mStyle = Paint.Style.FILL.nativeInt; + mCap = Paint.Cap.BUTT.nativeInt; + mJoin = Paint.Join.MITER.nativeInt; + mTextAlign = 0; + mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance); + mStrokeWidth = 1.f; + mStrokeMiter = 4.f; + mTextSize = 20.f; + mTextScaleX = 1.f; + mTextSkewX = 0.f; + mXfermode = null; + mColorFilter = null; + mShader = null; + mPathEffect = null; + mMaskFilter = null; + mRasterizer = null; + updateFontObject(); + } + + /** + * Update the {@link Font} object from the typeface, text size and scaling + */ + @SuppressWarnings("deprecation") + private void updateFontObject() { + if (mTypeface != null) { + // Get the fonts from the TypeFace object. + List<Font> fonts = mTypeface.getFonts(); + + // create new font objects as well as FontMetrics, based on the current text size + // and skew info. + ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size()); + for (Font font : fonts) { + FontInfo info = new FontInfo(); + info.mFont = font.deriveFont(mTextSize); + if (mTextScaleX != 1.0 || mTextSkewX != 0) { + // TODO: support skew + info.mFont = info.mFont.deriveFont(new AffineTransform( + mTextScaleX, mTextSkewX, 0, 0, 1, 0)); + } + info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont); + + infoList.add(info); + } + + mFonts = Collections.unmodifiableList(infoList); + } + } + + /*package*/ float measureText(char[] text, int index, int count) { + + // WARNING: the logic in this method is similar to Canvas_Delegate.native_drawText + // Any change to this method should be reflected there as well + + if (mFonts.size() > 0) { + FontInfo mainFont = mFonts.get(0); + int i = index; + int lastIndex = index + count; + float total = 0f; + while (i < lastIndex) { + // always start with the main font. + int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); + if (upTo == -1) { + // shortcut to exit + return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i); + } else if (upTo > 0) { + total += mainFont.mMetrics.charsWidth(text, i, upTo - i); + i = upTo; + // don't call continue at this point. Since it is certain the main font + // cannot display the font a index upTo (now ==i), we move on to the + // fallback fonts directly. + } + + // no char supported, attempt to read the next char(s) with the + // fallback font. In this case we only test the first character + // and then go back to test with the main font. + // Special test for 2-char characters. + boolean foundFont = false; + for (int f = 1 ; f < mFonts.size() ; f++) { + FontInfo fontInfo = mFonts.get(f); + + // need to check that the font can display the character. We test + // differently if the char is a high surrogate. + int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; + upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); + if (upTo == -1) { + total += fontInfo.mMetrics.charsWidth(text, i, charCount); + i += charCount; + foundFont = true; + break; + + } + } + + // in case no font can display the char, measure it with the main font. + if (foundFont == false) { + int size = Character.isHighSurrogate(text[i]) ? 2 : 1; + total += mainFont.mMetrics.charsWidth(text, i, size); + i += size; + } + } + + return total; + } + + return 0; + } + + private float getFontMetrics(FontMetrics metrics) { + if (mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; + if (metrics != null) { + // Android expects negative ascent so we invert the value from Java. + metrics.top = - javaMetrics.getMaxAscent(); + metrics.ascent = - javaMetrics.getAscent(); + metrics.descent = javaMetrics.getDescent(); + metrics.bottom = javaMetrics.getMaxDescent(); + metrics.leading = javaMetrics.getLeading(); + } + + return javaMetrics.getHeight(); + } + + return 0; + } + + + + private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + if (flagValue) { + delegate.mFlags |= flagMask; + } else { + delegate.mFlags &= ~flagMask; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Path.java b/tools/layoutlib/bridge/src/android/graphics/Path.java deleted file mode 100644 index 12d2cde..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Path.java +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Copyright (C) 2006 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.Shape; -import java.awt.geom.AffineTransform; -import java.awt.geom.Ellipse2D; -import java.awt.geom.GeneralPath; -import java.awt.geom.PathIterator; -import java.awt.geom.Rectangle2D; - -/** - * The Path class encapsulates compound (multiple contour) geometric paths - * consisting of straight line segments, quadratic curves, and cubic curves. - * It can be drawn with canvas.drawPath(path, paint), either filled or stroked - * (based on the paint's Style), or it can be used for clipping or to draw - * 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() { - return mPath; - } - - //---------- - - /** - * Create an empty path - */ - public Path() { - } - - /** - * Create a new path, copying the contents from the src path. - * - * @param src The path to copy from when initializing the new 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. - */ - public void reset() { - mPath = new GeneralPath(); - } - - /** - * Rewinds the path: clears any lines and curves from the path but - * keeps the internal data structure for faster reuse. - */ - public void rewind() { - // FIXME - throw new UnsupportedOperationException(); - } - - /** Replace the contents of this with the contents of src. - */ - public void set(Path src) { - mPath.append(src.mPath, false /* connect */); - } - - /** Enum for the ways a path may be filled - */ - public enum FillType { - // these must match the values in SkPath.h - WINDING (GeneralPath.WIND_NON_ZERO, false), - 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; - } - - final int rule; - final boolean inverse; - } - - /** - * Return the path's fill type. This defines how "inside" is - * computed. The default value is WINDING. - * - * @return the path's fill type - */ - public FillType getFillType() { - return mFillType; - } - - /** - * Set the path's fill type. This defines how "inside" is computed. - * - * @param ft The new fill type for this path - */ - public void setFillType(FillType ft) { - mFillType = ft; - mPath.setWindingRule(ft.rule); - } - - /** - * Returns true if the filltype is one of the INVERSE variants - * - * @return true if the filltype is one of the INVERSE variants - */ - public boolean isInverseFillType() { - return mFillType.inverse; - } - - /** - * Toggles the INVERSE state of the filltype - */ - public void toggleInverseFillType() { - switch (mFillType) { - case WINDING: - mFillType = FillType.INVERSE_WINDING; - break; - case EVEN_ODD: - mFillType = FillType.INVERSE_EVEN_ODD; - break; - case INVERSE_WINDING: - mFillType = FillType.WINDING; - break; - case INVERSE_EVEN_ODD: - mFillType = FillType.EVEN_ODD; - break; - } - } - - /** - * Returns true if the path is empty (contains no lines or curves) - * - * @return true if the path is empty (contains no lines or curves) - */ - public boolean isEmpty() { - return mPath.getCurrentPoint() == null; - } - - /** - * Returns true if the path specifies a rectangle. If so, and if rect is - * not null, set rect to the bounds of the path. If the path does not - * specify a rectangle, return false and ignore rect. - * - * @param rect If not null, returns the bounds of the path if it specifies - * a rectangle - * @return true if the path specifies a rectangle - */ - public boolean isRect(RectF rect) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Compute the bounds of the path, and write the answer into bounds. If the - * path contains 0 or 1 points, the bounds is set to (0,0,0,0) - * - * @param bounds Returns the computed bounds of the path - * @param exact If true, return the exact (but slower) bounds, else return - * just the bounds of all control points - */ - public void computeBounds(RectF bounds, boolean exact) { - Rectangle2D rect = mPath.getBounds2D(); - bounds.left = (float)rect.getMinX(); - bounds.right = (float)rect.getMaxX(); - bounds.top = (float)rect.getMinY(); - bounds.bottom = (float)rect.getMaxY(); - } - - /** - * Hint to the path to prepare for adding more points. This can allow the - * path to more efficiently allocate its storage. - * - * @param extraPtCount The number of extra points that may be added to this - * path - */ - public void incReserve(int extraPtCount) { - // pass - } - - /** - * Set the beginning of the next contour to the point (x,y). - * - * @param x The x-coordinate of the start of a new contour - * @param y The y-coordinate of the start of a new contour - */ - public void moveTo(float x, float y) { - mPath.moveTo(mLastX = x, mLastY = y); - } - - /** - * Set the beginning of the next contour relative to the last point on the - * previous contour. If there is no previous contour, this is treated the - * same as moveTo(). - * - * @param dx The amount to add to the x-coordinate of the end of the - * previous contour, to specify the start of a new contour - * @param dy The amount to add to the y-coordinate of the end of the - * previous contour, to specify the start of a new contour - */ - public void rMoveTo(float dx, float dy) { - dx += mLastX; - dy += mLastY; - mPath.moveTo(mLastX = dx, mLastY = dy); - } - - /** - * Add a line from the last point to the specified point (x,y). - * If no moveTo() call has been made for this contour, the first point is - * automatically set to (0,0). - * - * @param x The x-coordinate of the end of a line - * @param y The y-coordinate of the end of a line - */ - public void lineTo(float x, float y) { - mPath.lineTo(mLastX = x, mLastY = y); - } - - /** - * Same as lineTo, but the coordinates are considered relative to the last - * point on this contour. If there is no previous point, then a moveTo(0,0) - * is inserted automatically. - * - * @param dx The amount to add to the x-coordinate of the previous point on - * this contour, to specify a line - * @param dy The amount to add to the y-coordinate of the previous point on - * this contour, to specify a line - */ - public void rLineTo(float dx, float dy) { - if (isEmpty()) { - mPath.moveTo(mLastX = 0, mLastY = 0); - } - dx += mLastX; - dy += mLastY; - mPath.lineTo(mLastX = dx, mLastY = dy); - } - - /** - * Add a quadratic bezier from the last point, approaching control point - * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for - * this contour, the first point is automatically set to (0,0). - * - * @param x1 The x-coordinate of the control point on a quadratic curve - * @param y1 The y-coordinate of the control point on a quadratic curve - * @param x2 The x-coordinate of the end point on a quadratic curve - * @param y2 The y-coordinate of the end point on a quadratic curve - */ - public void quadTo(float x1, float y1, float x2, float y2) { - mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2); - } - - /** - * Same as quadTo, but the coordinates are considered relative to the last - * point on this contour. If there is no previous point, then a moveTo(0,0) - * is inserted automatically. - * - * @param dx1 The amount to add to the x-coordinate of the last point on - * this contour, for the control point of a quadratic curve - * @param dy1 The amount to add to the y-coordinate of the last point on - * this contour, for the control point of a quadratic curve - * @param dx2 The amount to add to the x-coordinate of the last point on - * this contour, for the end point of a quadratic curve - * @param dy2 The amount to add to the y-coordinate of the last point on - * this contour, for the end point of a quadratic curve - */ - public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { - if (isEmpty()) { - mPath.moveTo(mLastX = 0, mLastY = 0); - } - dx1 += mLastX; - dy1 += mLastY; - dx2 += mLastX; - dy2 += mLastY; - mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2); - } - - /** - * Add a cubic bezier from the last point, approaching control points - * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been - * made for this contour, the first point is automatically set to (0,0). - * - * @param x1 The x-coordinate of the 1st control point on a cubic curve - * @param y1 The y-coordinate of the 1st control point on a cubic curve - * @param x2 The x-coordinate of the 2nd control point on a cubic curve - * @param y2 The y-coordinate of the 2nd control point on a cubic curve - * @param x3 The x-coordinate of the end point on a cubic curve - * @param y3 The y-coordinate of the end point on a cubic curve - */ - public void cubicTo(float x1, float y1, float x2, float y2, - float x3, float y3) { - mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3); - } - - /** - * Same as cubicTo, but the coordinates are considered relative to the - * current point on this contour. If there is no previous point, then a - * moveTo(0,0) is inserted automatically. - */ - public void rCubicTo(float dx1, float dy1, float dx2, float dy2, - float dx3, float dy3) { - if (isEmpty()) { - mPath.moveTo(mLastX = 0, mLastY = 0); - } - dx1 += mLastX; - dy1 += mLastY; - dx2 += mLastX; - dy2 += mLastY; - dx3 += mLastX; - dy3 += mLastY; - mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3); - } - - /** - * 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 - * automatic lineTo() is added to connect the current contour to the - * start of the arc. However, if the path is empty, then we call moveTo() - * with the first point of the arc. The sweep angle is tread mod 360. - * - * @param oval The bounds of oval defining shape and size of the arc - * @param startAngle Starting angle (in degrees) where the arc begins - * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated - * mod 360. - * @param forceMoveTo If true, always begin a new contour with the arc - */ - public void arcTo(RectF oval, float startAngle, float sweepAngle, - 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 - * automatic lineTo() is added to connect the current contour to the - * start of the arc. However, if the path is empty, then we call moveTo() - * with the first point of the arc. - * - * @param oval The bounds of oval defining shape and size of the arc - * @param startAngle Starting angle (in degrees) where the arc begins - * @param sweepAngle Sweep angle (in degrees) measured clockwise - */ - 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. - */ - public void close() { - mPath.closePath(); - } - - /** - * Specifies how closed shapes (e.g. rects, ovals) are oriented when they - * are added to a path. - */ - public enum Direction { - /** clockwise */ - 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 - * - * @param rect The rectangle to add as a closed contour to the path - * @param dir The direction to wind the rectangle's contour - */ - public void addRect(RectF rect, Direction dir) { - if (rect == null) { - throw new NullPointerException("need rect parameter"); - } - - addRect(rect.left, rect.top, rect.right, rect.bottom, dir); - } - - /** - * Add a closed rectangle contour to the path - * - * @param left The left side of a rectangle to add to the path - * @param top The top of a rectangle to add to the path - * @param right The right side of a rectangle to add to the path - * @param bottom The bottom of a rectangle to add to the path - * @param dir The direction to wind the rectangle's contour - */ - public void addRect(float left, float top, float right, float bottom, - Direction dir) { - moveTo(left, top); - - switch (dir) { - case CW: - lineTo(right, top); - lineTo(right, bottom); - lineTo(left, bottom); - break; - case CCW: - lineTo(left, bottom); - lineTo(right, bottom); - lineTo(right, top); - break; - } - - close(); - } - - /** - * Add a closed oval contour to the path - * - * @param oval The bounds of the oval to add as a closed contour to the path - * @param dir The direction to wind the oval's contour - */ - public void addOval(RectF oval, Direction dir) { - if (oval == null) { - throw new NullPointerException("need oval parameter"); - } - - // FIXME Need to support direction - Ellipse2D ovalShape = new Ellipse2D.Float(oval.left, oval.top, oval.width(), oval.height()); - - mPath.append(ovalShape, false /* connect */); - } - - /** - * Add a closed circle contour to the path - * - * @param x The x-coordinate of the center of a circle to add to the path - * @param y The y-coordinate of the center of a circle to add to the path - * @param radius The radius of a circle to add to the path - * @param dir The direction to wind the circle's contour - */ - public void addCircle(float x, float y, float radius, Direction dir) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add the specified arc to the path as a new contour. - * - * @param oval The bounds of oval defining the shape and size of the arc - * @param startAngle Starting angle (in degrees) where the arc begins - * @param sweepAngle Sweep angle (in degrees) measured clockwise - */ - public void addArc(RectF oval, float startAngle, float sweepAngle) { - if (oval == null) { - throw new NullPointerException("need oval parameter"); - } - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add a closed round-rectangle contour to the path - * - * @param rect The bounds of a round-rectangle to add to the path - * @param rx The x-radius of the rounded corners on the round-rectangle - * @param ry The y-radius of the rounded corners on the round-rectangle - * @param dir The direction to wind the round-rectangle's contour - */ - public void addRoundRect(RectF rect, float rx, float ry, Direction dir) { - if (rect == null) { - throw new NullPointerException("need rect parameter"); - } - // 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, - * bottom-right, bottom-left - * - * @param rect The bounds of a round-rectangle to add to the path - * @param radii Array of 8 values, 4 pairs of [X,Y] radii - * @param dir The direction to wind the round-rectangle's contour - */ - public void addRoundRect(RectF rect, float[] radii, Direction dir) { - if (rect == null) { - throw new NullPointerException("need rect parameter"); - } - if (radii.length < 8) { - throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); - } - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add a copy of src to the path, offset by (dx,dy) - * - * @param src The path to add as a new contour - * @param dx The amount to translate the path in X as it is added - */ - public void addPath(Path src, float dx, float dy) { - PathIterator iterator = src.mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); - mPath.append(iterator, false /* connect */); - } - - /** - * Add a copy of src to the path - * - * @param src The path that is appended to the current path - */ - public void addPath(Path src) { - addPath(src, 0, 0); - } - - /** - * Add a copy of src to the path, transformed by matrix - * - * @param src The path to add as a new contour - */ - public void addPath(Path src, Matrix matrix) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Offset the path by (dx,dy), returning true on success - * - * @param dx The amount in the X direction to offset the entire path - * @param dy The amount in the Y direction to offset the entire path - * @param dst The translated path is written here. If this is null, then - * the original path is modified. - */ - 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 { - mPath = newPath; - } - } - - /** - * Offset the path by (dx,dy), returning true on success - * - * @param dx The amount in the X direction to offset the entire path - * @param dy The amount in the Y direction to offset the entire path - */ - public void offset(float dx, float dy) { - offset(dx, dy, null /* dst */); - } - - /** - * Sets the last point of the path. - * - * @param dx The new X coordinate for the last point - * @param dy The new Y coordinate for the last point - */ - public void setLastPoint(float dx, float dy) { - mLastX = dx; - mLastY = dy; - } - - /** - * Transform the points in this path by matrix, and write the answer - * into dst. If dst is null, then the the original path is modified. - * - * @param matrix The matrix to apply to the path - * @param dst The transformed path is written here. If dst is null, - * then the the original path is modified - */ - public void transform(Matrix matrix, Path dst) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Transform the points in this path by matrix. - * - * @param matrix The matrix to apply to the path - */ - public void transform(Matrix matrix) { - transform(matrix, null /* dst */); - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java new file mode 100644 index 0000000..c448f0e --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java @@ -0,0 +1,72 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.PathDashPathEffect + * + * Through the layoutlib_create tool, the original native methods of PathDashPathEffect 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 PathDashPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class PathDashPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Path Dash Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int native_path, float advance, float phase, + int native_style) { + PathDashPathEffect_Delegate newDelegate = new PathDashPathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java new file mode 100644 index 0000000..bd2b6de --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java @@ -0,0 +1,69 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.PathEffect + * + * Through the layoutlib_create tool, the original native methods of PathEffect 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 PathEffect class. + * + * This also serve as a base class for all PathEffect delegate classes. + * + * @see DelegateManager + * + */ +public abstract class PathEffect_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<PathEffect_Delegate> sManager = + new DelegateManager<PathEffect_Delegate>(PathEffect_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static PathEffect_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract Stroke getStroke(Paint_Delegate paint); + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_patheffect) { + sManager.removeJavaReferenceFor(native_patheffect); + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java new file mode 100644 index 0000000..6c9f48f --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -0,0 +1,761 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Path.Direction; +import android.graphics.Path.FillType; + +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +/** + * Delegate implementing the native methods of android.graphics.Path + * + * Through the layoutlib_create tool, the original native methods of Path 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 Path class. + * + * @see DelegateManager + * + */ +public final class Path_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Path_Delegate> sManager = + new DelegateManager<Path_Delegate>(Path_Delegate.class); + + // ---- delegate data ---- + private FillType mFillType = FillType.WINDING; + private GeneralPath mPath = new GeneralPath(); + + private float mLastX = 0; + private float mLastY = 0; + + // ---- Public Helper methods ---- + + public static Path_Delegate getDelegate(int nPath) { + return sManager.getDelegate(nPath); + } + + public Shape getJavaShape() { + return mPath; + } + + public void setJavaShape(Shape shape) { + mPath.reset(); + mPath.append(shape, false /*connect*/); + } + + public void reset() { + mPath.reset(); + } + + public void setPathIterator(PathIterator iterator) { + mPath.reset(); + mPath.append(iterator, false /*connect*/); + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int init1() { + // create the delegate + Path_Delegate newDelegate = new Path_Delegate(); + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int init2(int nPath) { + // create the delegate + Path_Delegate newDelegate = new Path_Delegate(); + + // get the delegate to copy, which could be null if nPath is 0 + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate != null) { + newDelegate.set(pathDelegate); + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_reset(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mPath.reset(); + } + + @LayoutlibDelegate + /*package*/ static void native_rewind(int nPath) { + // call out to reset since there's nothing to optimize in + // terms of data structs. + native_reset(nPath); + } + + @LayoutlibDelegate + /*package*/ static void native_set(int native_dst, int native_src) { + Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst); + if (pathDstDelegate == null) { + return; + } + + Path_Delegate pathSrcDelegate = sManager.getDelegate(native_src); + if (pathSrcDelegate == null) { + return; + } + + pathDstDelegate.set(pathSrcDelegate); + } + + @LayoutlibDelegate + /*package*/ static int native_getFillType(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return 0; + } + + return pathDelegate.mFillType.nativeInt; + } + + @LayoutlibDelegate + /*package*/ static void native_setFillType(int nPath, int ft) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mFillType = Path.sFillTypeArray[ft]; + } + + @LayoutlibDelegate + /*package*/ static boolean native_isEmpty(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return true; + } + + return pathDelegate.isEmpty(); + } + + @LayoutlibDelegate + /*package*/ static boolean native_isRect(int nPath, RectF rect) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return false; + } + + // create an Area that can test if the path is a rect + Area area = new Area(pathDelegate.mPath); + if (area.isRectangular()) { + if (rect != null) { + pathDelegate.fillBounds(rect); + } + + return true; + } + + return false; + } + + @LayoutlibDelegate + /*package*/ static void native_computeBounds(int nPath, RectF bounds) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.fillBounds(bounds); + } + + @LayoutlibDelegate + /*package*/ static void native_incReserve(int nPath, int extraPtCount) { + // since we use a java2D path, there's no way to pre-allocate new points, + // so we do nothing. + } + + @LayoutlibDelegate + /*package*/ static void native_moveTo(int nPath, float x, float y) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.moveTo(x, y); + } + + @LayoutlibDelegate + /*package*/ static void native_rMoveTo(int nPath, float dx, float dy) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rMoveTo(dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void native_lineTo(int nPath, float x, float y) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.lineTo(x, y); + } + + @LayoutlibDelegate + /*package*/ static void native_rLineTo(int nPath, float dx, float dy) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rLineTo(dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void native_quadTo(int nPath, float x1, float y1, float x2, float y2) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.quadTo(x1, y1, x2, y2); + } + + @LayoutlibDelegate + /*package*/ static void native_rQuadTo(int nPath, float dx1, float dy1, float dx2, float dy2) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rQuadTo(dx1, dy1, dx2, dy2); + } + + @LayoutlibDelegate + /*package*/ static void native_cubicTo(int nPath, float x1, float y1, + float x2, float y2, float x3, float y3) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3); + } + + @LayoutlibDelegate + /*package*/ static void native_rCubicTo(int nPath, float x1, float y1, + float x2, float y2, float x3, float y3) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3); + } + + @LayoutlibDelegate + /*package*/ static void native_arcTo(int nPath, RectF oval, + float startAngle, float sweepAngle, boolean forceMoveTo) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo); + } + + @LayoutlibDelegate + /*package*/ static void native_close(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.close(); + } + + @LayoutlibDelegate + /*package*/ static void native_addRect(int nPath, RectF rect, int dir) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir); + } + + @LayoutlibDelegate + /*package*/ static void native_addRect(int nPath, + float left, float top, float right, float bottom, int dir) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.addRect(left, top, right, bottom, dir); + } + + @LayoutlibDelegate + /*package*/ static void native_addOval(int nPath, RectF oval, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addOval is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addCircle(int nPath, float x, float y, float radius, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addCircle is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addArc(int nPath, RectF oval, + float startAngle, float sweepAngle) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addArc is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addRoundRect(int nPath, RectF rect, + float rx, float ry, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addRoundRect is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addRoundRect(int nPath, RectF r, float[] radii, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addRoundRect is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addPath(int nPath, int src, float dx, float dy) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addPath(int nPath, int src) { + native_addPath(nPath, src, 0, 0); + } + + @LayoutlibDelegate + /*package*/ static void native_addPath(int nPath, int src, int matrix) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_offset(int nPath, float dx, float dy, int dst_path) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + // could be null if the int is 0; + Path_Delegate dstDelegate = sManager.getDelegate(dst_path); + + pathDelegate.offset(dx, dy, dstDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_offset(int nPath, float dx, float dy) { + native_offset(nPath, dx, dy, 0); + } + + @LayoutlibDelegate + /*package*/ static void native_setLastPoint(int nPath, float dx, float dy) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mLastX = dx; + pathDelegate.mLastY = dy; + } + + @LayoutlibDelegate + /*package*/ static void native_transform(int nPath, int matrix, + int dst_path) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix); + if (matrixDelegate == null) { + return; + } + + // this can be null if dst_path is 0 + Path_Delegate dstDelegate = sManager.getDelegate(dst_path); + + pathDelegate.transform(matrixDelegate, dstDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_transform(int nPath, int matrix) { + native_transform(nPath, matrix, 0); + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int nPath) { + sManager.removeJavaReferenceFor(nPath); + } + + + // ---- Private helper methods ---- + + private void set(Path_Delegate delegate) { + mPath.reset(); + setFillType(delegate.mFillType); + mPath.append(delegate.mPath, false /*connect*/); + } + + private void setFillType(FillType fillType) { + mFillType = fillType; + mPath.setWindingRule(getWindingRule(fillType)); + } + + /** + * Returns the Java2D winding rules matching a given Android {@link FillType}. + * @param type the android fill type + * @return the matching java2d winding rule. + */ + private static int getWindingRule(FillType type) { + switch (type) { + case WINDING: + case INVERSE_WINDING: + return GeneralPath.WIND_NON_ZERO; + case EVEN_ODD: + case INVERSE_EVEN_ODD: + return GeneralPath.WIND_EVEN_ODD; + } + + assert false; + throw new IllegalArgumentException(); + } + + private static Direction getDirection(int direction) { + for (Direction d : Direction.values()) { + if (direction == d.nativeInt) { + return d; + } + } + + assert false; + return null; + } + + /** + * Returns whether the path is empty. + * @return true if the path is empty. + */ + private boolean isEmpty() { + return mPath.getCurrentPoint() == null; + } + + /** + * Fills the given {@link RectF} with the path bounds. + * @param bounds the RectF to be filled. + */ + private void fillBounds(RectF bounds) { + Rectangle2D rect = mPath.getBounds2D(); + bounds.left = (float)rect.getMinX(); + bounds.right = (float)rect.getMaxX(); + bounds.top = (float)rect.getMinY(); + bounds.bottom = (float)rect.getMaxY(); + } + + /** + * Set the beginning of the next contour to the point (x,y). + * + * @param x The x-coordinate of the start of a new contour + * @param y The y-coordinate of the start of a new contour + */ + private void moveTo(float x, float y) { + mPath.moveTo(mLastX = x, mLastY = y); + } + + /** + * Set the beginning of the next contour relative to the last point on the + * previous contour. If there is no previous contour, this is treated the + * same as moveTo(). + * + * @param dx The amount to add to the x-coordinate of the end of the + * previous contour, to specify the start of a new contour + * @param dy The amount to add to the y-coordinate of the end of the + * previous contour, to specify the start of a new contour + */ + private void rMoveTo(float dx, float dy) { + dx += mLastX; + dy += mLastY; + mPath.moveTo(mLastX = dx, mLastY = dy); + } + + /** + * Add a line from the last point to the specified point (x,y). + * If no moveTo() call has been made for this contour, the first point is + * automatically set to (0,0). + * + * @param x The x-coordinate of the end of a line + * @param y The y-coordinate of the end of a line + */ + private void lineTo(float x, float y) { + mPath.lineTo(mLastX = x, mLastY = y); + } + + /** + * Same as lineTo, but the coordinates are considered relative to the last + * point on this contour. If there is no previous point, then a moveTo(0,0) + * is inserted automatically. + * + * @param dx The amount to add to the x-coordinate of the previous point on + * this contour, to specify a line + * @param dy The amount to add to the y-coordinate of the previous point on + * this contour, to specify a line + */ + private void rLineTo(float dx, float dy) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } + dx += mLastX; + dy += mLastY; + mPath.lineTo(mLastX = dx, mLastY = dy); + } + + /** + * Add a quadratic bezier from the last point, approaching control point + * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for + * this contour, the first point is automatically set to (0,0). + * + * @param x1 The x-coordinate of the control point on a quadratic curve + * @param y1 The y-coordinate of the control point on a quadratic curve + * @param x2 The x-coordinate of the end point on a quadratic curve + * @param y2 The y-coordinate of the end point on a quadratic curve + */ + private void quadTo(float x1, float y1, float x2, float y2) { + mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2); + } + + /** + * Same as quadTo, but the coordinates are considered relative to the last + * point on this contour. If there is no previous point, then a moveTo(0,0) + * is inserted automatically. + * + * @param dx1 The amount to add to the x-coordinate of the last point on + * this contour, for the control point of a quadratic curve + * @param dy1 The amount to add to the y-coordinate of the last point on + * this contour, for the control point of a quadratic curve + * @param dx2 The amount to add to the x-coordinate of the last point on + * this contour, for the end point of a quadratic curve + * @param dy2 The amount to add to the y-coordinate of the last point on + * this contour, for the end point of a quadratic curve + */ + private void rQuadTo(float dx1, float dy1, float dx2, float dy2) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } + dx1 += mLastX; + dy1 += mLastY; + dx2 += mLastX; + dy2 += mLastY; + mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2); + } + + /** + * Add a cubic bezier from the last point, approaching control points + * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been + * made for this contour, the first point is automatically set to (0,0). + * + * @param x1 The x-coordinate of the 1st control point on a cubic curve + * @param y1 The y-coordinate of the 1st control point on a cubic curve + * @param x2 The x-coordinate of the 2nd control point on a cubic curve + * @param y2 The y-coordinate of the 2nd control point on a cubic curve + * @param x3 The x-coordinate of the end point on a cubic curve + * @param y3 The y-coordinate of the end point on a cubic curve + */ + private void cubicTo(float x1, float y1, float x2, float y2, + float x3, float y3) { + mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3); + } + + /** + * Same as cubicTo, but the coordinates are considered relative to the + * current point on this contour. If there is no previous point, then a + * moveTo(0,0) is inserted automatically. + */ + private void rCubicTo(float dx1, float dy1, float dx2, float dy2, + float dx3, float dy3) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } + dx1 += mLastX; + dy1 += mLastY; + dx2 += mLastX; + dy2 += mLastY; + dx3 += mLastX; + dy3 += mLastY; + mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3); + } + + /** + * 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 + * automatic lineTo() is added to connect the current contour to the + * start of the arc. However, if the path is empty, then we call moveTo() + * with the first point of the arc. The sweep angle is tread mod 360. + * + * @param oval The bounds of oval defining shape and size of the arc + * @param startAngle Starting angle (in degrees) where the arc begins + * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated + * mod 360. + * @param forceMoveTo If true, always begin a new contour with the arc + */ + private void arcTo(RectF oval, float startAngle, float sweepAngle, + boolean forceMoveTo) { + Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), startAngle, + sweepAngle, Arc2D.OPEN); + mPath.append(arc, true /*connect*/); + + resetLastPointFromPath(); + } + + /** + * Close the current contour. If the current point is not equal to the + * first point of the contour, a line segment is automatically added. + */ + private void close() { + mPath.closePath(); + } + + private void resetLastPointFromPath() { + Point2D last = mPath.getCurrentPoint(); + mLastX = (float) last.getX(); + mLastY = (float) last.getY(); + } + + /** + * Add a closed rectangle contour to the path + * + * @param left The left side of a rectangle to add to the path + * @param top The top of a rectangle to add to the path + * @param right The right side of a rectangle to add to the path + * @param bottom The bottom of a rectangle to add to the path + * @param dir The direction to wind the rectangle's contour + */ + private void addRect(float left, float top, float right, float bottom, + int dir) { + moveTo(left, top); + + Direction direction = getDirection(dir); + + switch (direction) { + case CW: + lineTo(right, top); + lineTo(right, bottom); + lineTo(left, bottom); + break; + case CCW: + lineTo(left, bottom); + lineTo(right, bottom); + lineTo(right, top); + break; + } + + close(); + + resetLastPointFromPath(); + } + + /** + * Offset the path by (dx,dy), returning true on success + * + * @param dx The amount in the X direction to offset the entire path + * @param dy The amount in the Y direction to offset the entire path + * @param dst The translated path is written here. If this is null, then + * the original path is modified. + */ + public void offset(float dx, float dy, Path_Delegate 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 { + mPath = newPath; + } + } + + /** + * Transform the points in this path by matrix, and write the answer + * into dst. If dst is null, then the the original path is modified. + * + * @param matrix The matrix to apply to the path + * @param dst The transformed path is written here. If dst is null, + * then the the original path is modified + */ + public void transform(Matrix_Delegate matrix, Path_Delegate dst) { + if (matrix.hasPerspective()) { + assert false; + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE, + "android.graphics.Path#transform() only " + + "supports affine transformations.", null, null /*data*/); + } + + GeneralPath newPath = new GeneralPath(); + + PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform()); + + newPath.append(iterator, false /*connect*/); + + if (dst != null) { + dst.mPath = newPath; + } else { + mPath = newPath; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java new file mode 100644 index 0000000..4ab044b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java @@ -0,0 +1,70 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.PixelXorXfermode + * + * Through the layoutlib_create tool, the original native methods of PixelXorXfermode 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 PixelXorXfermode class. + * + * Because this extends {@link Xfermode_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link Xfermode_Delegate}. + * + * @see Xfermode_Delegate + */ +public class PixelXorXfermode_Delegate extends Xfermode_Delegate { + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Composite getComposite(int alpha) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Pixel XOR Xfermodes are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int opColor) { + PixelXorXfermode_Delegate newDelegate = new PixelXorXfermode_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java new file mode 100644 index 0000000..c45dbaa --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.PorterDuffColorFilter + * + * Through the layoutlib_create tool, the original native methods of PorterDuffColorFilter 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 PorterDuffColorFilter class. + * + * Because this extends {@link ColorFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link ColorFilter_Delegate}. + * + * @see ColorFilter_Delegate + * + */ +public class PorterDuffColorFilter_Delegate extends ColorFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "PorterDuff Color Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) { + PorterDuffColorFilter_Delegate newDelegate = new PorterDuffColorFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nCreatePorterDuffFilter(int nativeFilter, int srcColor, + int porterDuffMode) { + // pass + return 0; + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java new file mode 100644 index 0000000..4301c1a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java @@ -0,0 +1,140 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.AlphaComposite; +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.PorterDuffXfermode + * + * Through the layoutlib_create tool, the original native methods of PorterDuffXfermode 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 PorterDuffXfermode class. + * + * Because this extends {@link Xfermode_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link Xfermode_Delegate}. + * + */ +public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { + + // ---- delegate data ---- + + private final int mMode; + + // ---- Public Helper methods ---- + + public PorterDuff.Mode getMode() { + return getPorterDuffMode(mMode); + } + + @Override + public Composite getComposite(int alpha) { + return getComposite(getPorterDuffMode(mMode), alpha); + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public String getSupportMessage() { + // no message since isSupported returns true; + return null; + } + + public static PorterDuff.Mode getPorterDuffMode(int mode) { + for (PorterDuff.Mode m : PorterDuff.Mode.values()) { + if (m.nativeInt == mode) { + return m; + } + } + + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("Unknown PorterDuff.Mode: %d", mode), null /*data*/); + assert false; + return PorterDuff.Mode.SRC_OVER; + } + + public static Composite getComposite(PorterDuff.Mode mode, int alpha) { + float falpha = alpha != 0xFF ? (float)alpha / 255.f : 1.f; + switch (mode) { + case CLEAR: + return AlphaComposite.getInstance(AlphaComposite.CLEAR, falpha); + case DARKEN: + break; + case DST: + return AlphaComposite.getInstance(AlphaComposite.DST, falpha); + case DST_ATOP: + return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha); + case DST_IN: + return AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha); + case DST_OUT: + return AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha); + case DST_OVER: + return AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha); + case LIGHTEN: + break; + case MULTIPLY: + break; + case SCREEN: + break; + case SRC: + return AlphaComposite.getInstance(AlphaComposite.SRC, falpha); + case SRC_ATOP: + return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha); + case SRC_IN: + return AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha); + case SRC_OUT: + return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha); + case SRC_OVER: + return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha); + case XOR: + return AlphaComposite.getInstance(AlphaComposite.XOR, falpha); + } + + Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, + String.format("Unsupported PorterDuff Mode: %s", mode.name()), + null, null /*data*/); + + return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha); + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreateXfermode(int mode) { + PorterDuffXfermode_Delegate newDelegate = new PorterDuffXfermode_Delegate(mode); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- + + private PorterDuffXfermode_Delegate(int mode) { + mMode = mode; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java deleted file mode 100644 index 4409a80..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java +++ /dev/null @@ -1,132 +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; - -public class RadialGradient extends GradientShader { - - private RadialGradientPaint mPaint; - - /** - * Create a shader that draws a radial gradient given the center and radius. - * - * @param x The x-coordinate of the center of the radius - * @param y The y-coordinate of the center of the radius - * @param radius Must be positive. The radius of the circle for this - * gradient - * @param colors The colors to be distributed between the center and edge of - * the circle - * @param positions May be NULL. The relative position of each corresponding - * color in the colors array. If this is NULL, the the colors are - * distributed evenly between the center and edge of the circle. - * @param tile The Shader tiling mode - */ - public RadialGradient(float x, float y, float radius, int colors[], float positions[], - TileMode tile) { - super(colors, positions); - if (radius <= 0) { - throw new IllegalArgumentException("radius must be > 0"); - } - - mPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile); - } - - /** - * Create a shader that draws a radial gradient given the center and radius. - * - * @param x The x-coordinate of the center of the radius - * @param y The y-coordinate of the center of the radius - * @param radius Must be positive. The radius of the circle for this - * gradient - * @param color0 The color at the center of the circle. - * @param color1 The color at the edge of the circle. - * @param tile The Shader tiling mode - */ - public RadialGradient(float x, float y, float radius, int color0, int color1, TileMode tile) { - this(x, y, radius, new int[] { color0, color1 }, null /* positions */, tile); - } - - @Override - java.awt.Paint getJavaPaint() { - return mPaint; - } - - private static class RadialGradientPaint extends GradientPaint { - - private final float mX; - private final float mY; - private final float mRadius; - - public RadialGradientPaint(float x, float y, float radius, int[] colors, float[] positions, TileMode mode) { - super(colors, positions, mode); - mX = x; - mY = y; - mRadius = radius; - } - - public java.awt.PaintContext createContext( - java.awt.image.ColorModel colorModel, - java.awt.Rectangle deviceBounds, - java.awt.geom.Rectangle2D userBounds, - java.awt.geom.AffineTransform xform, - java.awt.RenderingHints hints) { - precomputeGradientColors(); - return new RadialGradientPaintContext(colorModel); - } - - private class RadialGradientPaintContext implements java.awt.PaintContext { - - private final java.awt.image.ColorModel mColorModel; - - public RadialGradientPaintContext(java.awt.image.ColorModel colorModel) { - mColorModel = colorModel; - } - - public void dispose() { - } - - public java.awt.image.ColorModel getColorModel() { - return mColorModel; - } - - public java.awt.image.Raster getRaster(int x, int y, int w, int h) { - java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, - java.awt.image.BufferedImage.TYPE_INT_ARGB); - - int[] data = new int[w*h]; - - // compute distance from each point to the center, and figure out the distance from - // it. - int index = 0; - for (int iy = 0 ; iy < h ; iy++) { - for (int ix = 0 ; ix < w ; ix++) { - float _x = x + ix - mX; - float _y = y + iy - mY; - float distance = (float) Math.sqrt(_x * _x + _y * _y); - - data[index++] = getGradientColor(distance / mRadius); - } - } - - image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); - - return image.getRaster(); - } - - } - } - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java new file mode 100644 index 0000000..9bf78b4 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java @@ -0,0 +1,211 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.RadialGradient + * + * Through the layoutlib_create tool, the original native methods of RadialGradient 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 RadialGradient class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class RadialGradient_Delegate extends Gradient_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(float x, float y, float radius, + int colors[], float positions[], int tileMode) { + RadialGradient_Delegate newDelegate = new RadialGradient_Delegate(x, y, radius, + colors, positions, Shader_Delegate.getTileMode(tileMode)); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(float x, float y, float radius, + int color0, int color1, int tileMode) { + return nativeCreate1(x, y, radius, new int[] { color0, color1 }, null /*positions*/, + tileMode); + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate1(int native_shader, float x, float y, float radius, + int colors[], float positions[], int tileMode) { + // nothing to be done here. + return 0; + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate2(int native_shader, float x, float y, float radius, + int color0, int color1, int tileMode) { + // nothing to be done here. + return 0; + } + + // ---- Private delegate/helper methods ---- + + /** + * Create a shader that draws a radial gradient given the center and radius. + * + * @param x The x-coordinate of the center of the radius + * @param y The y-coordinate of the center of the radius + * @param radius Must be positive. The radius of the circle for this + * gradient + * @param colors The colors to be distributed between the center and edge of + * the circle + * @param positions May be NULL. The relative position of each corresponding + * color in the colors array. If this is NULL, the the colors are + * distributed evenly between the center and edge of the circle. + * @param tile The Shader tiling mode + */ + private RadialGradient_Delegate(float x, float y, float radius, int colors[], float positions[], + TileMode tile) { + super(colors, positions); + mJavaPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile); + } + + private class RadialGradientPaint extends GradientPaint { + + private final float mX; + private final float mY; + private final float mRadius; + + public RadialGradientPaint(float x, float y, float radius, + int[] colors, float[] positions, TileMode mode) { + super(colors, positions, mode); + mX = x; + mY = y; + mRadius = radius; + } + + public java.awt.PaintContext createContext( + java.awt.image.ColorModel colorModel, + java.awt.Rectangle deviceBounds, + java.awt.geom.Rectangle2D userBounds, + java.awt.geom.AffineTransform xform, + java.awt.RenderingHints hints) { + precomputeGradientColors(); + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in RadialGradient", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in RadialGradient", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new RadialGradientPaintContext(canvasMatrix, localMatrix, colorModel); + } + + private class RadialGradientPaintContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + public RadialGradientPaintContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + mColorModel = colorModel; + } + + public void dispose() { + } + + public java.awt.image.ColorModel getColorModel() { + return mColorModel; + } + + public java.awt.image.Raster getRaster(int x, int y, int w, int h) { + java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, + java.awt.image.BufferedImage.TYPE_INT_ARGB); + + int[] data = new int[w*h]; + + // compute distance from each point to the center, and figure out the distance from + // it. + int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix + pt1[0] = pt2[0] - mX; + pt1[1] = pt2[1] - mY; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + float _x = pt2[0]; + float _y = pt2[1]; + float distance = (float) Math.sqrt(_x * _x + _y * _y); + + data[index++] = getGradientColor(distance / mRadius); + } + } + + image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); + + return image.getRaster(); + } + + } + } + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java new file mode 100644 index 0000000..2812b6b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.Rasterizer + * + * Through the layoutlib_create tool, the original native methods of Rasterizer 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 Rasterizer class. + * + * This also serve as a base class for all Rasterizer delegate classes. + * + * @see DelegateManager + * + */ +public abstract class Rasterizer_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Rasterizer_Delegate> sManager = + new DelegateManager<Rasterizer_Delegate>(Rasterizer_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static Rasterizer_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java new file mode 100644 index 0000000..cb31b8f --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java @@ -0,0 +1,484 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.os.Parcel; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.Rectangle2D; + +/** + * Delegate implementing the native methods of android.graphics.Region + * + * Through the layoutlib_create tool, the original native methods of Region 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 Region class. + * + * This also serve as a base class for all Region delegate classes. + * + * @see DelegateManager + * + */ +public class Region_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Region_Delegate> sManager = + new DelegateManager<Region_Delegate>(Region_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + private Area mArea = new Area(); + + // ---- Public Helper methods ---- + + public static Region_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public Area getJavaArea() { + return mArea; + } + + /** + * Combines two {@link Shape} into another one (actually an {@link Area}), according + * to the given {@link Region.Op}. + * + * If the Op is not one that combines two shapes, then this return null + * + * @param shape1 the firt shape to combine which can be null if there's no original clip. + * @param shape2 the 2nd shape to combine + * @param regionOp the operande for the combine + * @return a new area or null. + */ + public static Area combineShapes(Shape shape1, Shape shape2, int regionOp) { + if (regionOp == Region.Op.DIFFERENCE.nativeInt) { + // if shape1 is null (empty), then the result is null. + if (shape1 == null) { + return null; + } + + // result is always a new area. + Area result = new Area(shape1); + result.subtract(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.INTERSECT.nativeInt) { + // if shape1 is null, then the result is simply shape2. + if (shape1 == null) { + return new Area(shape2); + } + + // result is always a new area. + Area result = new Area(shape1); + result.intersect(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.UNION.nativeInt) { + // if shape1 is null, then the result is simply shape2. + if (shape1 == null) { + return new Area(shape2); + } + + // result is always a new area. + Area result = new Area(shape1); + result.add(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.XOR.nativeInt) { + // if shape1 is null, then the result is simply shape2 + if (shape1 == null) { + return new Area(shape2); + } + + // result is always a new area. + Area result = new Area(shape1); + result.exclusiveOr(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) { + // result is always a new area. + Area result = new Area(shape2); + + if (shape1 != null) { + result.subtract(shape1 instanceof Area ? (Area) shape1 : new Area(shape1)); + } + + return result; + } + + return null; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static boolean isEmpty(Region thisRegion) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return true; + } + + return regionDelegate.mArea.isEmpty(); + } + + @LayoutlibDelegate + /*package*/ static boolean isRect(Region thisRegion) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return true; + } + + return regionDelegate.mArea.isRectangular(); + } + + @LayoutlibDelegate + /*package*/ static boolean isComplex(Region thisRegion) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return true; + } + + return regionDelegate.mArea.isSingular() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean contains(Region thisRegion, int x, int y) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + return regionDelegate.mArea.contains(x, y); + } + + @LayoutlibDelegate + /*package*/ static boolean quickContains(Region thisRegion, + int left, int top, int right, int bottom) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + return regionDelegate.mArea.isRectangular() && + regionDelegate.mArea.contains(left, top, right - left, bottom - top); + } + + @LayoutlibDelegate + /*package*/ static boolean quickReject(Region thisRegion, + int left, int top, int right, int bottom) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + return regionDelegate.mArea.isEmpty() || + regionDelegate.mArea.intersects(left, top, right - left, bottom - top) == false; + } + + @LayoutlibDelegate + /*package*/ static boolean quickReject(Region thisRegion, Region rgn) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + Region_Delegate targetRegionDelegate = sManager.getDelegate(rgn.mNativeRegion); + if (targetRegionDelegate == null) { + return false; + } + + return regionDelegate.mArea.isEmpty() || + regionDelegate.mArea.getBounds().intersects( + targetRegionDelegate.mArea.getBounds()) == false; + + } + + @LayoutlibDelegate + /*package*/ static void translate(Region thisRegion, int dx, int dy, Region dst) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return; + } + + Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion); + if (targetRegionDelegate == null) { + return; + } + + if (regionDelegate.mArea.isEmpty()) { + targetRegionDelegate.mArea = new Area(); + } else { + targetRegionDelegate.mArea = new Area(regionDelegate.mArea); + AffineTransform mtx = new AffineTransform(); + mtx.translate(dx, dy); + targetRegionDelegate.mArea.transform(mtx); + } + } + + @LayoutlibDelegate + /*package*/ static void scale(Region thisRegion, float scale, Region dst) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return; + } + + Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion); + if (targetRegionDelegate == null) { + return; + } + + if (regionDelegate.mArea.isEmpty()) { + targetRegionDelegate.mArea = new Area(); + } else { + targetRegionDelegate.mArea = new Area(regionDelegate.mArea); + AffineTransform mtx = new AffineTransform(); + mtx.scale(scale, scale); + targetRegionDelegate.mArea.transform(mtx); + } + } + + @LayoutlibDelegate + /*package*/ static int nativeConstructor() { + Region_Delegate newDelegate = new Region_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_region) { + sManager.removeJavaReferenceFor(native_region); + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSetRegion(int native_dst, int native_src) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + Region_Delegate srcRegion = sManager.getDelegate(native_src); + if (srcRegion == null) { + return true; + } + + dstRegion.mArea.reset(); + dstRegion.mArea.add(srcRegion.mArea); + + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSetRect(int native_dst, + int left, int top, int right, int bottom) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + dstRegion.mArea = new Area(new Rectangle2D.Float(left, top, right - left, bottom - top)); + return dstRegion.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSetPath(int native_dst, int native_path, int native_clip) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + Path_Delegate path = Path_Delegate.getDelegate(native_path); + if (path == null) { + return true; + } + + dstRegion.mArea = new Area(path.getJavaShape()); + + Region_Delegate clip = sManager.getDelegate(native_clip); + if (clip != null) { + dstRegion.mArea.subtract(clip.getJavaArea()); + } + + return dstRegion.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeGetBounds(int native_region, Rect rect) { + Region_Delegate region = sManager.getDelegate(native_region); + if (region == null) { + return true; + } + + Rectangle bounds = region.mArea.getBounds(); + if (bounds.isEmpty()) { + rect.left = rect.top = rect.right = rect.bottom = 0; + return false; + } + + rect.left = bounds.x; + rect.top = bounds.y; + rect.right = bounds.x + bounds.width; + rect.bottom = bounds.y + bounds.height; + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeGetBoundaryPath(int native_region, int native_path) { + Region_Delegate region = sManager.getDelegate(native_region); + if (region == null) { + return false; + } + + Path_Delegate path = Path_Delegate.getDelegate(native_path); + if (path == null) { + return false; + } + + if (region.mArea.isEmpty()) { + path.reset(); + return false; + } + + path.setPathIterator(region.mArea.getPathIterator(new AffineTransform())); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeOp(int native_dst, + int left, int top, int right, int bottom, int op) { + Region_Delegate region = sManager.getDelegate(native_dst); + if (region == null) { + return false; + } + + region.mArea = combineShapes(region.mArea, + new Rectangle2D.Float(left, top, right - left, bottom - top), op); + + assert region.mArea != null; + if (region.mArea != null) { + region.mArea = new Area(); + } + + return region.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeOp(int native_dst, Rect rect, int native_region, int op) { + Region_Delegate region = sManager.getDelegate(native_dst); + if (region == null) { + return false; + } + + region.mArea = combineShapes(region.mArea, + new Rectangle2D.Float(rect.left, rect.top, rect.width(), rect.height()), op); + + assert region.mArea != null; + if (region.mArea != null) { + region.mArea = new Area(); + } + + return region.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeOp(int native_dst, + int native_region1, int native_region2, int op) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + Region_Delegate region1 = sManager.getDelegate(native_region1); + if (region1 == null) { + return false; + } + + Region_Delegate region2 = sManager.getDelegate(native_region2); + if (region2 == null) { + return false; + } + + dstRegion.mArea = combineShapes(region1.mArea, region2.mArea, op); + + assert dstRegion.mArea != null; + if (dstRegion.mArea != null) { + dstRegion.mArea = new Area(); + } + + return dstRegion.mArea.getBounds().isEmpty() == false; + + } + + @LayoutlibDelegate + /*package*/ static int nativeCreateFromParcel(Parcel p) { + // This is only called by Region.CREATOR (Parcelable.Creator<Region>), which is only + // used during aidl call so really this should not be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Regions cannot be created from parcels.", + null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeWriteToParcel(int native_region, + Parcel p) { + // This is only called when sending a region through aidl, so really this should not + // be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Regions cannot be written to parcels.", + null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeEquals(int native_r1, int native_r2) { + Region_Delegate region1 = sManager.getDelegate(native_r1); + if (region1 == null) { + return false; + } + + Region_Delegate region2 = sManager.getDelegate(native_r2); + if (region2 == null) { + return false; + } + + return region1.mArea.equals(region2.mArea); + } + + @LayoutlibDelegate + /*package*/ static String nativeToString(int native_region) { + Region_Delegate region = sManager.getDelegate(native_region); + if (region == null) { + return "not found"; + } + + return region.mArea.toString(); + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader.java b/tools/layoutlib/bridge/src/android/graphics/Shader.java deleted file mode 100644 index 0cc5940..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Shader.java +++ /dev/null @@ -1,83 +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; - - - -/** - * Shader is the based class for objects that return horizontal spans of colors - * during drawing. A subclass of Shader is installed in a Paint calling - * paint.setShader(shader). After that any object (other than a bitmap) that is - * drawn with that paint will get its color(s) from the shader. - */ -public abstract class Shader { - - private final Matrix mMatrix = new Matrix(); - - public enum TileMode { - /** - * replicate the edge color if the shader draws outside of its - * original bounds - */ - CLAMP (0), - /** - * repeat the shader's image horizontally and vertically - */ - REPEAT (1), - /** - * repeat the shader's image horizontally and vertically, alternating - * mirror images so that adjacent images always seam - */ - MIRROR (2); - - TileMode(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - /** - * Return true if the shader has a non-identity local matrix. - * @param localM If not null, it is set to the shader's local matrix. - * @return true if the shader has a non-identity local matrix - */ - public boolean getLocalMatrix(Matrix localM) { - if (localM != null) { - localM.set(mMatrix); - } - - return !mMatrix.isIdentity(); - } - - /** - * Set the shader's local matrix. Passing null will reset the shader's - * matrix to identity - * @param localM The shader's new local matrix, or null to specify identity - */ - public void setLocalMatrix(Matrix localM) { - if (localM != null) { - mMatrix.set(localM); - } else { - mMatrix.reset(); - } - } - - /** - * Returns a java.awt.Paint object matching this shader. - */ - abstract java.awt.Paint getJavaPaint(); -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java new file mode 100644 index 0000000..368c0384 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java @@ -0,0 +1,105 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.Shader + * + * Through the layoutlib_create tool, the original native methods of Shader 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 Shader class. + * + * This also serve as a base class for all Shader delegate classes. + * + * @see DelegateManager + * + */ +public abstract class Shader_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Shader_Delegate> sManager = + new DelegateManager<Shader_Delegate>(Shader_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + private Matrix_Delegate mLocalMatrix = null; + + // ---- Public Helper methods ---- + + public static Shader_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + /** + * Returns the {@link TileMode} matching the given int. + * @param tileMode the tile mode int value + * @return the TileMode enum. + */ + public static TileMode getTileMode(int tileMode) { + for (TileMode tm : TileMode.values()) { + if (tm.nativeInt == tileMode) { + return tm; + } + } + + assert false; + return TileMode.CLAMP; + } + + public abstract java.awt.Paint getJavaPaint(); + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_shader, int native_skiaShader) { + sManager.removeJavaReferenceFor(native_shader); + } + + @LayoutlibDelegate + /*package*/ static void nativeSetLocalMatrix(int native_shader, int native_skiaShader, + int matrix_instance) { + // get the delegate from the native int. + Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader); + if (shaderDelegate == null) { + return; + } + + shaderDelegate.mLocalMatrix = Matrix_Delegate.getDelegate(matrix_instance); + } + + // ---- Private delegate/helper methods ---- + + protected java.awt.geom.AffineTransform getLocalMatrix() { + if (mLocalMatrix != null) { + return mLocalMatrix.getAffineTransform(); + } + + return new java.awt.geom.AffineTransform(); + } + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java new file mode 100644 index 0000000..410df0c --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.SumPathEffect + * + * Through the layoutlib_create tool, the original native methods of SumPathEffect 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 SumPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class SumPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Sum Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int first, int second) { + SumPathEffect_Delegate newDelegate = new SumPathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java deleted file mode 100644 index 87036ed..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java +++ /dev/null @@ -1,140 +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; - -public class SweepGradient extends GradientShader { - - private SweepGradientPaint mPaint; - - /** - * A subclass of Shader that draws a sweep gradient around a center point. - * - * @param cx The x-coordinate of the center - * @param cy The y-coordinate of the center - * @param colors The colors to be distributed between around the center. - * There must be at least 2 colors in the array. - * @param positions May be NULL. The relative position of - * each corresponding color in the colors array, beginning - * with 0 and ending with 1.0. If the values are not - * monotonic, the drawing may produce unexpected results. - * If positions is NULL, then the colors are automatically - * spaced evenly. - */ - public SweepGradient(float cx, float cy, - int colors[], float positions[]) { - super(colors, positions); - - mPaint = new SweepGradientPaint(cx, cy, mColors, mPositions); - } - - /** - * A subclass of Shader that draws a sweep gradient around a center point. - * - * @param cx The x-coordinate of the center - * @param cy The y-coordinate of the center - * @param color0 The color to use at the start of the sweep - * @param color1 The color to use at the end of the sweep - */ - public SweepGradient(float cx, float cy, int color0, int color1) { - this(cx, cy, new int[] { color0, color1}, null /*positions*/); - } - - @Override - java.awt.Paint getJavaPaint() { - return mPaint; - } - - private static class SweepGradientPaint extends GradientPaint { - - private final float mCx; - private final float mCy; - - public SweepGradientPaint(float cx, float cy, int[] colors, float[] positions) { - super(colors, positions, null /*tileMode*/); - mCx = cx; - mCy = cy; - } - - public java.awt.PaintContext createContext( - java.awt.image.ColorModel colorModel, - java.awt.Rectangle deviceBounds, - java.awt.geom.Rectangle2D userBounds, - java.awt.geom.AffineTransform xform, - java.awt.RenderingHints hints) { - precomputeGradientColors(); - return new SweepGradientPaintContext(colorModel); - } - - private class SweepGradientPaintContext implements java.awt.PaintContext { - - private final java.awt.image.ColorModel mColorModel; - - public SweepGradientPaintContext(java.awt.image.ColorModel colorModel) { - mColorModel = colorModel; - } - - public void dispose() { - } - - public java.awt.image.ColorModel getColorModel() { - return mColorModel; - } - - public java.awt.image.Raster getRaster(int x, int y, int w, int h) { - java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, - java.awt.image.BufferedImage.TYPE_INT_ARGB); - - int[] data = new int[w*h]; - - // compute angle from each point to the center, and figure out the distance from - // it. - int index = 0; - for (int iy = 0 ; iy < h ; iy++) { - for (int ix = 0 ; ix < w ; ix++) { - float dx = x + ix - mCx; - float dy = y + iy - mCy; - float angle; - if (dx == 0) { - angle = (float) (dy < 0 ? 3 * Math.PI / 2 : Math.PI / 2); - } else if (dy == 0) { - angle = (float) (dx < 0 ? Math.PI : 0); - } else { - angle = (float) Math.atan(dy / dx); - if (dx > 0) { - if (dy < 0) { - angle += Math.PI * 2; - } - } else { - angle += Math.PI; - } - } - - // convert to 0-1. value and get color - data[index++] = getGradientColor((float) (angle / (2 * Math.PI))); - } - } - - image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); - - return image.getRaster(); - } - - } - } - -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java new file mode 100644 index 0000000..966e06e --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java @@ -0,0 +1,218 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.SweepGradient + * + * Through the layoutlib_create tool, the original native methods of SweepGradient 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 SweepGradient class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class SweepGradient_Delegate extends Gradient_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(float x, float y, int colors[], float positions[]) { + SweepGradient_Delegate newDelegate = new SweepGradient_Delegate(x, y, colors, positions); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(float x, float y, int color0, int color1) { + return nativeCreate1(x, y, new int[] { color0, color1 }, null /*positions*/); + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate1(int native_shader, float cx, float cy, + int[] colors, float[] positions) { + // nothing to be done here. + return 0; + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate2(int native_shader, float cx, float cy, + int color0, int color1) { + // nothing to be done here. + return 0; + } + + // ---- Private delegate/helper methods ---- + + /** + * A subclass of Shader that draws a sweep gradient around a center point. + * + * @param cx The x-coordinate of the center + * @param cy The y-coordinate of the center + * @param colors The colors to be distributed between around the center. + * There must be at least 2 colors in the array. + * @param positions May be NULL. The relative position of + * each corresponding color in the colors array, beginning + * with 0 and ending with 1.0. If the values are not + * monotonic, the drawing may produce unexpected results. + * If positions is NULL, then the colors are automatically + * spaced evenly. + */ + private SweepGradient_Delegate(float cx, float cy, + int colors[], float positions[]) { + super(colors, positions); + mJavaPaint = new SweepGradientPaint(cx, cy, mColors, mPositions); + } + + private class SweepGradientPaint extends GradientPaint { + + private final float mCx; + private final float mCy; + + public SweepGradientPaint(float cx, float cy, int[] colors, + float[] positions) { + super(colors, positions, null /*tileMode*/); + mCx = cx; + mCy = cy; + } + + public java.awt.PaintContext createContext( + java.awt.image.ColorModel colorModel, + java.awt.Rectangle deviceBounds, + java.awt.geom.Rectangle2D userBounds, + java.awt.geom.AffineTransform xform, + java.awt.RenderingHints hints) { + precomputeGradientColors(); + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in SweepGradient", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in SweepGradient", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new SweepGradientPaintContext(canvasMatrix, localMatrix, colorModel); + } + + private class SweepGradientPaintContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + public SweepGradientPaintContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + mColorModel = colorModel; + } + + public void dispose() { + } + + public java.awt.image.ColorModel getColorModel() { + return mColorModel; + } + + public java.awt.image.Raster getRaster(int x, int y, int w, int h) { + java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, + java.awt.image.BufferedImage.TYPE_INT_ARGB); + + int[] data = new int[w*h]; + + // compute angle from each point to the center, and figure out the distance from + // it. + int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix + pt1[0] = pt2[0] - mCx; + pt1[1] = pt2[1] - mCy; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + float dx = pt2[0]; + float dy = pt2[1]; + + float angle; + if (dx == 0) { + angle = (float) (dy < 0 ? 3 * Math.PI / 2 : Math.PI / 2); + } else if (dy == 0) { + angle = (float) (dx < 0 ? Math.PI : 0); + } else { + angle = (float) Math.atan(dy / dx); + if (dx > 0) { + if (dy < 0) { + angle += Math.PI * 2; + } + } else { + angle += Math.PI; + } + } + + // convert to 0-1. value and get color + data[index++] = getGradientColor((float) (angle / (2 * Math.PI))); + } + } + + image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); + + return image.getRaster(); + } + + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface.java b/tools/layoutlib/bridge/src/android/graphics/Typeface.java deleted file mode 100644 index af3adb5..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface.java +++ /dev/null @@ -1,190 +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 com.android.layoutlib.bridge.FontLoader; - -import android.content.res.AssetManager; - -import java.awt.Font; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Re-implementation of Typeface over java.awt - */ -public class Typeface { - private static final String DEFAULT_FAMILY = "sans-serif"; - private static final int[] styleBuffer = new int[1]; - - /** The default NORMAL typeface object */ - public static Typeface DEFAULT; - /** - * The default BOLD typeface object. Note: this may be not actually be - * bold, depending on what fonts are installed. Call getStyle() to know - * for sure. - */ - public static Typeface DEFAULT_BOLD; - /** The NORMAL style of the default sans serif typeface. */ - public static Typeface SANS_SERIF; - /** The NORMAL style of the default serif typeface. */ - public static Typeface SERIF; - /** The NORMAL style of the default monospace typeface. */ - public static Typeface MONOSPACE; - - private static Typeface[] sDefaults; - private static FontLoader mFontLoader; - - private final int mStyle; - private final List<Font> mFonts; - private final String mFamily; - - // Style - public static final int NORMAL = _Original_Typeface.NORMAL; - public static final int BOLD = _Original_Typeface.BOLD; - public static final int ITALIC = _Original_Typeface.ITALIC; - public static final int BOLD_ITALIC = _Original_Typeface.BOLD_ITALIC; - - /** - * Returns the underlying {@link Font} objects. The first item in the list is the real - * font. Any other items are fallback fonts for characters not found in the first one. - */ - public List<Font> getFonts() { - return mFonts; - } - - /** Returns the typeface's intrinsic style attributes */ - public int getStyle() { - return mStyle; - } - - /** Returns true if getStyle() has the BOLD bit set. */ - public final boolean isBold() { - return (getStyle() & BOLD) != 0; - } - - /** Returns true if getStyle() has the ITALIC bit set. */ - public final boolean isItalic() { - return (getStyle() & ITALIC) != 0; - } - - /** - * Create a typeface object given a family name, and option style information. - * If null is passed for the name, then the "default" font will be chosen. - * The resulting typeface object can be queried (getStyle()) to discover what - * its "real" style characteristics are. - * - * @param familyName May be null. The name of the font family. - * @param style The style (normal, bold, italic) of the typeface. - * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC - * @return The best matching typeface. - */ - public static Typeface create(String familyName, int style) { - styleBuffer[0] = style; - Font font = mFontLoader.getFont(familyName, styleBuffer); - if (font != null) { - ArrayList<Font> list = new ArrayList<Font>(); - list.add(font); - list.addAll(mFontLoader.getFallBackFonts()); - return new Typeface(familyName, styleBuffer[0], list); - } - - return null; - } - - /** - * Create a typeface object that best matches the specified existing - * typeface and the specified Style. Use this call if you want to pick a new - * style from the same family of an existing typeface object. If family is - * null, this selects from the default font's family. - * - * @param family May be null. The name of the existing type face. - * @param style The style (normal, bold, italic) of the typeface. - * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC - * @return The best matching typeface. - */ - public static Typeface create(Typeface family, int style) { - styleBuffer[0] = style; - Font font = mFontLoader.getFont(family.mFamily, styleBuffer); - if (font != null) { - ArrayList<Font> list = new ArrayList<Font>(); - list.add(font); - list.addAll(mFontLoader.getFallBackFonts()); - return new Typeface(family.mFamily, styleBuffer[0], list); - } - - return null; - } - - /** - * Returns one of the default typeface objects, based on the specified style - * - * @return the default typeface that corresponds to the style - */ - public static Typeface defaultFromStyle(int style) { - return sDefaults[style]; - } - - /** - * Create a new typeface from the specified font data. - * @param mgr The application's asset manager - * @param path The file name of the font data in the assets directory - * @return The new typeface. - */ - public static Typeface createFromAsset(AssetManager mgr, String path) { - return null; - //return new Typeface(nativeCreateFromAsset(mgr, path)); - } - - // don't allow clients to call this directly - private Typeface(String family, int style, List<Font> fonts) { - mFamily = family; - mFonts = Collections.unmodifiableList(fonts); - mStyle = style; - } - - public static void init(FontLoader fontLoader) { - mFontLoader = fontLoader; - - DEFAULT = create(DEFAULT_FAMILY, NORMAL); - DEFAULT_BOLD = create(DEFAULT_FAMILY, BOLD); - SANS_SERIF = create("sans-serif", NORMAL); - SERIF = create("serif", NORMAL); - MONOSPACE = create("monospace", NORMAL); - sDefaults = new Typeface[] { - DEFAULT, - DEFAULT_BOLD, - create(DEFAULT_FAMILY, ITALIC), - create(DEFAULT_FAMILY, BOLD_ITALIC), - }; - - /* - DEFAULT = create((String)null, 0); - DEFAULT_BOLD = create((String)null, Typeface.BOLD); - SANS_SERIF = create("sans-serif", 0); - SERIF = create("serif", 0); - MONOSPACE = create("monospace", 0); - - sDefaults = new Typeface[] { - DEFAULT, - DEFAULT_BOLD, - create((String)null, Typeface.ITALIC), - create((String)null, Typeface.BOLD_ITALIC), - };*/ - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java new file mode 100644 index 0000000..0f084f7 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -0,0 +1,191 @@ +/* + * 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.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.FontLoader; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.res.AssetManager; + +import java.awt.Font; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Delegate implementing the native methods of android.graphics.Typeface + * + * Through the layoutlib_create tool, the original native methods of Typeface 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 Typeface class. + * + * @see DelegateManager + * + */ +public final class Typeface_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Typeface_Delegate> sManager = + new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class); + + // ---- delegate helper data ---- + private static final String DEFAULT_FAMILY = "sans-serif"; + private static final int[] STYLE_BUFFER = new int[1]; + + private static FontLoader sFontLoader; + private static final List<Typeface_Delegate> sPostInitDelegate = + new ArrayList<Typeface_Delegate>(); + + // ---- delegate data ---- + + private final String mFamily; + private int mStyle; + private List<Font> mFonts; + + + // ---- Public Helper methods ---- + + public static synchronized void init(FontLoader fontLoader) { + sFontLoader = fontLoader; + + for (Typeface_Delegate delegate : sPostInitDelegate) { + delegate.init(); + } + sPostInitDelegate.clear(); + } + + public static Typeface_Delegate getDelegate(int nativeTypeface) { + return sManager.getDelegate(nativeTypeface); + } + + public static List<Font> getFonts(Typeface typeface) { + return getFonts(typeface.native_instance); + } + + public static List<Font> getFonts(int native_int) { + Typeface_Delegate delegate = sManager.getDelegate(native_int); + if (delegate == null) { + return null; + } + + return delegate.getFonts(); + } + + public List<Font> getFonts() { + return mFonts; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreate(String familyName, int style) { + if (familyName == null) { + familyName = DEFAULT_FAMILY; + } + + Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style); + if (sFontLoader != null) { + newDelegate.init(); + } else { + // font loader has not been initialized yet, add the delegate to a list of delegates + // to init when the font loader is initialized. + // There won't be any rendering before this happens anyway. + sPostInitDelegate.add(newDelegate); + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreateFromTypeface(int native_instance, int style) { + Typeface_Delegate delegate = sManager.getDelegate(native_instance); + if (delegate == null) { + return 0; + } + + Typeface_Delegate newDelegate = new Typeface_Delegate(delegate.mFamily, style); + if (sFontLoader != null) { + newDelegate.init(); + } else { + // font loader has not been initialized yet, add the delegate to a list of delegates + // to init when the font loader is initialized. + // There won't be any rendering before this happens anyway. + sPostInitDelegate.add(newDelegate); + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreateFromAsset(AssetManager mgr, String path) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Typeface.createFromAsset() is not supported.", null /*throwable*/, null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreateFromFile(String path) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Typeface.createFromFile() is not supported.", null /*throwable*/, null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static void nativeUnref(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + @LayoutlibDelegate + /*package*/ static int nativeGetStyle(int native_instance) { + Typeface_Delegate delegate = sManager.getDelegate(native_instance); + if (delegate == null) { + return 0; + } + + return delegate.mStyle; + } + + @LayoutlibDelegate + /*package*/ static void setGammaForText(float blackGamma, float whiteGamma) { + // This is for device testing only: pass + } + + // ---- Private delegate/helper methods ---- + + private Typeface_Delegate(String family, int style) { + mFamily = family; + mStyle = style; + } + + private void init() { + STYLE_BUFFER[0] = mStyle; + Font font = sFontLoader.getFont(mFamily, STYLE_BUFFER); + if (font != null) { + List<Font> list = new ArrayList<Font>(); + list.add(font); + list.addAll(sFontLoader.getFallBackFonts()); + mFonts = Collections.unmodifiableList(list); + mStyle = STYLE_BUFFER[0]; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java new file mode 100644 index 0000000..962d69c --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java @@ -0,0 +1,69 @@ +/* + * 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.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.Xfermode + * + * Through the layoutlib_create tool, the original native methods of Xfermode 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 Xfermode class. + * + * This also serve as a base class for all Xfermode delegate classes. + * + * @see DelegateManager + * + */ +public abstract class Xfermode_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Xfermode_Delegate> sManager = + new DelegateManager<Xfermode_Delegate>(Xfermode_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static Xfermode_Delegate getDelegate(int native_instance) { + return sManager.getDelegate(native_instance); + } + + public abstract Composite getComposite(int alpha); + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/os/Build_Delegate.java b/tools/layoutlib/bridge/src/android/os/Build_Delegate.java new file mode 100644 index 0000000..ff82a5e --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/Build_Delegate.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 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.os; + +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.util.Map; + +/** + * Delegate implementing the native methods of android.os.Build + * + * Through the layoutlib_create tool, the original native methods of Build have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +public class Build_Delegate { + + @LayoutlibDelegate + /*package*/ static String getString(String property) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(property); + if (value != null) { + return value; + } + + return Build.UNKNOWN; + } + +} diff --git a/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java new file mode 100644 index 0000000..2152c8a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java @@ -0,0 +1,57 @@ +/* + * 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.os; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + + +/** + * Delegate overriding selected methods of android.os.Handler + * + * Through the layoutlib_create tool, selected methods of Handler have been replaced + * by calls to methods of the same name in this delegate class. + * + * + */ +public class Handler_Delegate { + + // -------- Delegate methods + + @LayoutlibDelegate + /*package*/ static boolean sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) { + // get the callback + IHandlerCallback callback = sCallbacks.get(); + if (callback != null) { + callback.sendMessageAtTime(handler, msg, uptimeMillis); + } + return true; + } + + // -------- Delegate implementation + + public interface IHandlerCallback { + void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis); + } + + private final static ThreadLocal<IHandlerCallback> sCallbacks = + new ThreadLocal<IHandlerCallback>(); + + public static void setCallback(IHandlerCallback callback) { + sCallbacks.set(callback); + } + +} diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java new file mode 100644 index 0000000..63711a7 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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.os; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.os.SystemClock + * + * Through the layoutlib_create tool, the original native methods of SystemClock have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +public class SystemClock_Delegate { + private static long sBootTime = System.currentTimeMillis(); + + @LayoutlibDelegate + /*package*/ static boolean setCurrentTimeMillis(long millis) { + return true; + } + + /** + * Returns milliseconds since boot, not counting time spent in deep sleep. + * <b>Note:</b> This value may get reset occasionally (before it would + * otherwise wrap around). + * + * @return milliseconds of non-sleep uptime since boot. + */ + @LayoutlibDelegate + /*package*/ static long uptimeMillis() { + return System.currentTimeMillis() - sBootTime; + } + + /** + * Returns milliseconds since boot, including time spent in sleep. + * + * @return elapsed milliseconds since boot. + */ + @LayoutlibDelegate + /*package*/ static long elapsedRealtime() { + return System.currentTimeMillis() - sBootTime; + } + + /** + * Returns milliseconds running in the current thread. + * + * @return elapsed milliseconds in the thread + */ + @LayoutlibDelegate + /*package*/ static long currentThreadTimeMillis() { + return System.currentTimeMillis(); + } +} diff --git a/tools/layoutlib/bridge/src/android/util/FloatMath.java b/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java index aae44f2..1df78c2 100644 --- a/tools/layoutlib/bridge/src/android/util/FloatMath.java +++ b/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java @@ -16,20 +16,23 @@ package android.util; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + /** - * Reimplements _Original_FloatMath with the standard libraries. - * - * Math routines similar to those found in {@link java.lang.Math}. Performs - * computations on {@code float} values directly without incurring the overhead - * of conversions to and from {@code double}. + * Delegate implementing the native methods of android.util.FloatMath + * + * Through the layoutlib_create tool, the original native methods of FloatMath have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. * - * <p>On one platform, {@code FloatMath.sqrt(100)} executes in one third of the - * time required by {@code java.lang.Math.sqrt(100)}.</p> */ -public class FloatMath { +/*package*/ final class FloatMath_Delegate { /** Prevents instantiation. */ - private FloatMath() {} + private FloatMath_Delegate() {} /** * Returns the float conversion of the most positive (i.e. closest to @@ -38,7 +41,8 @@ public class FloatMath { * @param value to be converted * @return the floor of value */ - public static float floor(float value) { + @LayoutlibDelegate + /*package*/ static float floor(float value) { return (float)Math.floor(value); } @@ -49,7 +53,8 @@ public class FloatMath { * @param value to be converted * @return the ceiling of value */ - public static float ceil(float value) { + @LayoutlibDelegate + /*package*/ static float ceil(float value) { return (float)Math.ceil(value); } @@ -59,7 +64,8 @@ public class FloatMath { * @param angle to compute the cosine of, in radians * @return the sine of angle */ - public static float sin(float angle) { + @LayoutlibDelegate + /*package*/ static float sin(float angle) { return (float)Math.sin(angle); } @@ -69,7 +75,8 @@ public class FloatMath { * @param angle to compute the cosine of, in radians * @return the cosine of angle */ - public static float cos(float angle) { + @LayoutlibDelegate + /*package*/ static float cos(float angle) { return (float)Math.cos(angle); } @@ -80,7 +87,8 @@ public class FloatMath { * @param value to compute sqrt of * @return the square root of value */ - public static float sqrt(float value) { + @LayoutlibDelegate + /*package*/ static float sqrt(float value) { return (float)Math.sqrt(value); } } diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java new file mode 100644 index 0000000..0f3cf57 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 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.view; + +import com.android.layoutlib.bridge.android.BridgeInflater; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.util.AttributeSet; + +import java.io.IOException; + +/** + * Delegate used to provide new implementation of a select few methods of {@link LayoutInflater} + * + * Through the layoutlib_create tool, the original methods of LayoutInflater have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class LayoutInflater_Delegate { + + /** + * Recursive method used to descend down the xml hierarchy and instantiate + * views, instantiate their children, and then call onFinishInflate(). + */ + @LayoutlibDelegate + /*package*/ static void rInflate(LayoutInflater thisInflater, + XmlPullParser parser, View parent, final AttributeSet attrs, + boolean finishInflate) throws XmlPullParserException, IOException { + + if (finishInflate == false) { + // this is a merge rInflate! + if (thisInflater instanceof BridgeInflater) { + ((BridgeInflater) thisInflater).setIsInMerge(true); + } + } + + // ---- START DEFAULT IMPLEMENTATION. + + final int depth = parser.getDepth(); + int type; + + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + final String name = parser.getName(); + + if (LayoutInflater.TAG_REQUEST_FOCUS.equals(name)) { + thisInflater.parseRequestFocus(parser, parent); + } else if (LayoutInflater.TAG_INCLUDE.equals(name)) { + if (parser.getDepth() == 0) { + throw new InflateException("<include /> cannot be the root element"); + } + thisInflater.parseInclude(parser, parent, attrs); + } else if (LayoutInflater.TAG_MERGE.equals(name)) { + throw new InflateException("<merge /> must be the root element"); + } else { + final View view = thisInflater.createViewFromTag(parent, name, attrs); + final ViewGroup viewGroup = (ViewGroup) parent; + final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); + thisInflater.rInflate(parser, view, attrs, true); + viewGroup.addView(view, params); + } + } + + if (finishInflate) parent.onFinishInflate(); + + // ---- END DEFAULT IMPLEMENTATION. + + if (finishInflate == false) { + // this is a merge rInflate! + if (thisInflater instanceof BridgeInflater) { + ((BridgeInflater) thisInflater).setIsInMerge(false); + } + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode.java b/tools/layoutlib/bridge/src/android/view/View_Delegate.java index 974ae49..8215f7c 100644 --- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode.java +++ b/tools/layoutlib/bridge/src/android/view/View_Delegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * 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. @@ -14,27 +14,21 @@ * limitations under the License. */ -package android.graphics; +package android.view; -import android.graphics.PorterDuff.Mode; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -public class PorterDuffXfermode extends Xfermode { - private final Mode mMode; +/** + * Delegate used to provide new implementation of a select few methods of {@link View} + * + * Through the layoutlib_create tool, the original methods of View have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class View_Delegate { - /** - * Create an xfermode that uses the specified porter-duff mode. - * - * @param mode The porter-duff mode that is applied - */ - public PorterDuffXfermode(PorterDuff.Mode mode) { - mMode = mode; - } - - //---------- Custom Methods - - public PorterDuff.Mode getMode() { - return mMode; + @LayoutlibDelegate + /*package*/ static boolean isInEditMode(View thisView) { + return true; } - - //---------- } diff --git a/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java new file mode 100644 index 0000000..bf998b8 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 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.internal.util; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + + +/** + * Delegate used to provide new implementation of a select few methods of {@link XmlUtils} + * + * Through the layoutlib_create tool, the original methods of XmlUtils have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class XmlUtils_Delegate { + + @LayoutlibDelegate + /*package*/ static final int convertValueToInt(CharSequence charSeq, int defaultValue) { + if (null == charSeq) + return defaultValue; + + String nm = charSeq.toString(); + + // This code is copied from the original implementation. The issue is that + // The Dalvik libraries are able to handle Integer.parse("XXXXXXXX", 16) where XXXXXXX + // is > 80000000 but the Java VM cannot. + + int sign = 1; + int index = 0; + int len = nm.length(); + int base = 10; + + if ('-' == nm.charAt(0)) { + sign = -1; + index++; + } + + if ('0' == nm.charAt(index)) { + // Quick check for a zero by itself + if (index == (len - 1)) + return 0; + + char c = nm.charAt(index + 1); + + if ('x' == c || 'X' == c) { + index += 2; + base = 16; + } else { + index++; + base = 8; + } + } + else if ('#' == nm.charAt(index)) { + index++; + base = 16; + } + + return ((int)Long.parseLong(nm.substring(index), base)) * sign; + } +} 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..acc7379 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -16,68 +16,47 @@ package com.android.layoutlib.bridge; -import com.android.internal.util.XmlUtils; -import com.android.layoutlib.api.ILayoutBridge; -import com.android.layoutlib.api.ILayoutLog; -import com.android.layoutlib.api.ILayoutResult; -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IStyleResourceValue; -import com.android.layoutlib.api.IXmlPullParser; -import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo; -import com.android.layoutlib.bridge.LayoutResult.LayoutViewInfo; -import com.android.ninepatch.NinePatch; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; +import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; + +import com.android.ide.common.rendering.api.Capability; +import com.android.ide.common.rendering.api.DrawableParams; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.layoutlib.bridge.android.BridgeAssetManager; +import com.android.layoutlib.bridge.impl.FontLoader; +import com.android.layoutlib.bridge.impl.RenderDrawable; +import com.android.layoutlib.bridge.impl.RenderSessionImpl; +import com.android.ninepatch.NinePatchChunk; +import com.android.resources.ResourceType; import com.android.tools.layoutlib.create.MethodAdapter; import com.android.tools.layoutlib.create.OverrideMethod; +import com.android.util.Pair; -import android.content.res.Configuration; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.Region; import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; +import android.graphics.Typeface_Delegate; import android.os.Looper; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.BridgeInflater; -import android.view.InputChannel; -import android.view.IWindow; -import android.view.IWindowSession; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewGroup; -import android.view.View.AttachInfo; -import android.view.View.MeasureSpec; -import android.view.WindowManager.LayoutParams; -import android.widget.FrameLayout; -import android.widget.TabHost; -import android.widget.TabWidget; +import java.io.File; import java.lang.ref.SoftReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.Collection; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; /** * Main entry point of the LayoutLib Bridge. * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call - * {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}. + * {@link #createScene(SceneParams)} */ -public final class Bridge implements ILayoutBridge { - - private static final int DEFAULT_TITLE_BAR_HEIGHT = 25; - private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; +public final class Bridge extends com.android.ide.common.rendering.api.Bridge { public static class StaticMethodNotImplementedException extends RuntimeException { private static final long serialVersionUID = 1L; @@ -88,84 +67,139 @@ public final class Bridge implements ILayoutBridge { } /** - * Maps from id to resource name/type. This is for android.R only. + * Lock to ensure only one rendering/inflating happens at a time. + * This is due to some singleton in the Android framework. */ - private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>(); + private final static ReentrantLock sLock = new ReentrantLock(); + + /** + * Maps from id to resource type/name. This is for android.R only. + */ + private final static Map<Integer, Pair<ResourceType, String>> sRMap = + new HashMap<Integer, Pair<ResourceType, String>>(); + /** * Same as sRMap except for int[] instead of int resources. This is for android.R only. */ - private final static Map<int[], String> sRArrayMap = new HashMap<int[], String>(); + private final static Map<IntArray, String> sRArrayMap = new HashMap<IntArray, String>(); /** * Reverse map compared to sRMap, resource type -> (resource name -> id). * This is for android.R only. */ - private final static Map<String, Map<String, Integer>> sRFullMap = - new HashMap<String, Map<String,Integer>>(); + private final static Map<ResourceType, Map<String, Integer>> sRFullMap = + new EnumMap<ResourceType, Map<String,Integer>>(ResourceType.class); private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = new HashMap<Object, Map<String, SoftReference<Bitmap>>>(); - private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache = - new HashMap<Object, Map<String, SoftReference<NinePatch>>>(); + private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache = + new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>(); private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<String, SoftReference<Bitmap>>(); - private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache = - new HashMap<String, SoftReference<NinePatch>>(); + private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache = + new HashMap<String, SoftReference<NinePatchChunk>>(); private static Map<String, Map<String, Integer>> sEnumValueMap; + private static Map<String, String> sPlatformProperties; /** - * A default logger than prints to stdout/stderr. + * int[] wrapper to use as keys in maps. */ - private final static ILayoutLog sDefaultLogger = new ILayoutLog() { - public void error(String message) { - System.err.println(message); + private final static class IntArray { + private int[] mArray; + + private IntArray() { + // do nothing } - public void error(Throwable t) { - String message = t.getMessage(); - if (message == null) { - message = t.getClass().getName(); - } + private IntArray(int[] a) { + mArray = a; + } + + private void set(int[] a) { + mArray = a; + } + + @Override + public int hashCode() { + return Arrays.hashCode(mArray); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + + IntArray other = (IntArray) obj; + if (!Arrays.equals(mArray, other.mArray)) return false; + return true; + } + } + /** Instance of IntArrayWrapper to be reused in {@link #resolveResourceId(int[])}. */ + private final static IntArray sIntArrayWrapper = new IntArray(); + + /** + * A default log than prints to stdout/stderr. + */ + private final static LayoutLog sDefaultLog = new LayoutLog() { + @Override + public void error(String tag, String message, Object data) { System.err.println(message); } - public void warning(String message) { + @Override + public void error(String tag, String message, Throwable throwable, Object data) { + System.err.println(message); + } + + @Override + public void warning(String tag, String message, Object data) { System.out.println(message); } }; /** - * Logger defined during a compute layout operation. - * <p/> - * This logger is generally set to {@link #sDefaultLogger} except during rendering - * operations when it might be set to a specific provided logger. - * <p/> - * To change this value, use a block synchronized on {@link #sDefaultLogger}. + * Current log. */ - private static ILayoutLog sLogger = sDefaultLogger; + private static LayoutLog sCurrentLog = sDefaultLog; - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutBridge#getApiLevel() - */ + private EnumSet<Capability> mCapabilities; + + @Override public int getApiLevel() { - return API_CURRENT; + return com.android.ide.common.rendering.api.Bridge.API_CURRENT; } - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutLibBridge#init(java.lang.String, java.util.Map) - */ - public boolean init( - String fontOsLocation, Map<String, Map<String, Integer>> enumValueMap) { - - return sinit(fontOsLocation, enumValueMap); + @Override + public EnumSet<Capability> getCapabilities() { + return mCapabilities; } - private static synchronized boolean sinit(String fontOsLocation, - Map<String, Map<String, Integer>> enumValueMap) { + @Override + public boolean init(Map<String,String> platformProperties, + File fontLocation, + Map<String, Map<String, Integer>> enumValueMap, + LayoutLog log) { + sPlatformProperties = platformProperties; + sEnumValueMap = enumValueMap; + + // don't use EnumSet.allOf(), because the bridge doesn't come with its specific version + // of layoutlib_api. It is provided by the client which could have a more recent version + // with newer, unsupported capabilities. + mCapabilities = EnumSet.of( + Capability.UNBOUND_RENDERING, + Capability.CUSTOM_BACKGROUND_COLOR, + Capability.RENDER, + Capability.LAYOUT_ONLY, + Capability.EMBEDDED_LAYOUT, + Capability.VIEW_MANIPULATION, + Capability.PLAY_ANIMATION, + Capability.ANIMATED_VIEW_MANIPULATION); + + + BridgeAssetManager.initSystem(); // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener // on static (native) methods which prints the signature on the console and @@ -182,12 +216,8 @@ public final class Bridge implements ILayoutBridge { OverrideMethod.setDefaultListener(new MethodAdapter() { @Override public void onInvokeV(String signature, boolean isNative, Object caller) { - if (sLogger != null) { - synchronized (sDefaultLogger) { - sLogger.error("Missing Stub: " + signature + - (isNative ? " (native)" : "")); - } - } + sDefaultLog.error(null, "Missing Stub: " + signature + + (isNative ? " (native)" : ""), null /*data*/); if (debug.equalsIgnoreCase("throw")) { // Throwing this exception doesn't seem that useful. It breaks @@ -200,293 +230,138 @@ public final class Bridge implements ILayoutBridge { }); } - // Override View.isInEditMode to return true. - // - // This allows custom views that are drawn in the Graphical Layout Editor to adapt their - // rendering for preview. Most important this let custom views know that they can't expect - // the rest of their activities to be alive. - OverrideMethod.setMethodListener("android.view.View#isInEditMode()Z", - new MethodAdapter() { - @Override - public int onInvokeI(String signature, boolean isNative, Object caller) { - return 1; - } - } - ); - // load the fonts. - FontLoader fontLoader = FontLoader.create(fontOsLocation); + FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath()); if (fontLoader != null) { - Typeface.init(fontLoader); + Typeface_Delegate.init(fontLoader); } else { return false; } - sEnumValueMap = enumValueMap; - // now parse com.android.internal.R (and only this one as android.R is a subset of // the internal version), and put the content in the maps. try { - // WARNING: this only works because the class is already loaded, and therefore - // the objects returned by Field.get() are the same as the ones used by - // the code accessing the R class. - // int[] does not implement equals/hashCode, and if the parsing used a different class - // loader for the R class, this would NOT work. Class<?> r = com.android.internal.R.class; for (Class<?> inner : r.getDeclaredClasses()) { - String resType = inner.getSimpleName(); - - Map<String, Integer> fullMap = new HashMap<String, Integer>(); - sRFullMap.put(resType, fullMap); - - for (Field f : inner.getDeclaredFields()) { - // only process static final fields. Since the final attribute may have - // been altered by layoutlib_create, we only check static - int modifiers = f.getModifiers(); - if (Modifier.isStatic(modifiers)) { - Class<?> type = f.getType(); - if (type.isArray() && type.getComponentType() == int.class) { - // if the object is an int[] we put it in sRArrayMap - sRArrayMap.put((int[]) f.get(null), f.getName()); - } else if (type == int.class) { - Integer value = (Integer) f.get(null); - sRMap.put(value, new String[] { f.getName(), resType }); - fullMap.put(f.getName(), value); - } else { - assert false; + String resTypeName = inner.getSimpleName(); + ResourceType resType = ResourceType.getEnum(resTypeName); + if (resType != null) { + Map<String, Integer> fullMap = new HashMap<String, Integer>(); + sRFullMap.put(resType, fullMap); + + for (Field f : inner.getDeclaredFields()) { + // only process static final fields. Since the final attribute may have + // been altered by layoutlib_create, we only check static + int modifiers = f.getModifiers(); + if (Modifier.isStatic(modifiers)) { + Class<?> type = f.getType(); + if (type.isArray() && type.getComponentType() == int.class) { + // if the object is an int[] we put it in sRArrayMap using an IntArray + // wrapper that properly implements equals and hashcode for the array + // objects, as required by the map contract. + sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName()); + } else if (type == int.class) { + Integer value = (Integer) f.get(null); + sRMap.put(value, Pair.of(resType, f.getName())); + fullMap.put(f.getName(), value); + } else { + assert false; + } } } } } - } catch (IllegalArgumentException e) { - // FIXME: log/return the error (there's no logger object at this point!) - e.printStackTrace(); - return false; - } catch (IllegalAccessException e) { - e.printStackTrace(); + } catch (Throwable throwable) { + if (log != null) { + log.error(LayoutLog.TAG_BROKEN, + "Failed to load com.android.internal.R from the layout library jar", + throwable); + } return false; } return true; } - /* - * For compatilibty purposes, we implement the old deprecated version of computeLayout. - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) - */ - @Deprecated - public ILayoutResult computeLayout(IXmlPullParser layoutDescription, - Object projectKey, - int screenWidth, int screenHeight, String themeName, - Map<String, Map<String, IResourceValue>> projectResources, - Map<String, Map<String, IResourceValue>> frameworkResources, - IProjectCallback customViewLoader, ILayoutLog logger) { - boolean isProjectTheme = false; - if (themeName.charAt(0) == '*') { - themeName = themeName.substring(1); - isProjectTheme = true; - } - - return computeLayout(layoutDescription, projectKey, - screenWidth, screenHeight, DisplayMetrics.DENSITY_DEFAULT, - DisplayMetrics.DENSITY_DEFAULT, DisplayMetrics.DENSITY_DEFAULT, - themeName, isProjectTheme, - projectResources, frameworkResources, customViewLoader, logger); - } + @Override + public boolean dispose() { + BridgeAssetManager.clearSystem(); - /* - * For compatilibty purposes, we implement the old deprecated version of computeLayout. - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) - */ - @Deprecated - public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, - int screenWidth, int screenHeight, String themeName, boolean isProjectTheme, - Map<String, Map<String, IResourceValue>> projectResources, - Map<String, Map<String, IResourceValue>> frameworkResources, - IProjectCallback customViewLoader, ILayoutLog logger) { - return computeLayout(layoutDescription, projectKey, - screenWidth, screenHeight, DisplayMetrics.DENSITY_DEFAULT, - DisplayMetrics.DENSITY_DEFAULT, DisplayMetrics.DENSITY_DEFAULT, - themeName, isProjectTheme, - projectResources, frameworkResources, customViewLoader, logger); - } + // dispose of the default typeface. + Typeface.sDefaults = null; - /* - * For compatilibty purposes, we implement the old deprecated version of computeLayout. - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) - */ - public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, - int screenWidth, int screenHeight, int density, float xdpi, float ydpi, - String themeName, boolean isProjectTheme, - Map<String, Map<String, IResourceValue>> projectResources, - Map<String, Map<String, IResourceValue>> frameworkResources, - IProjectCallback customViewLoader, ILayoutLog logger) { - return computeLayout(layoutDescription, projectKey, - screenWidth, screenHeight, false /* renderFullSize */, - density, xdpi, ydpi, themeName, isProjectTheme, - projectResources, frameworkResources, customViewLoader, logger); + return true; } - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, boolean, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) + /** + * Starts a layout session by inflating and rendering it. The method returns a + * {@link RenderSession} on which further actions can be taken. + * + * @param params the {@link SessionParams} object with all the information necessary to create + * the scene. + * @return a new {@link RenderSession} object that contains the result of the layout. + * @since 5 */ - public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, - int screenWidth, int screenHeight, boolean renderFullSize, - int density, float xdpi, float ydpi, - String themeName, boolean isProjectTheme, - Map<String, Map<String, IResourceValue>> projectResources, - Map<String, Map<String, IResourceValue>> frameworkResources, - IProjectCallback customViewLoader, ILayoutLog logger) { - if (logger == null) { - logger = sDefaultLogger; - } - - synchronized (sDefaultLogger) { - sLogger = logger; - } - - // find the current theme and compute the style inheritance map - Map<IStyleResourceValue, IStyleResourceValue> styleParentMap = - new HashMap<IStyleResourceValue, IStyleResourceValue>(); - - IStyleResourceValue currentTheme = computeStyleMaps(themeName, isProjectTheme, - projectResources.get(BridgeConstants.RES_STYLE), - frameworkResources.get(BridgeConstants.RES_STYLE), styleParentMap); - - BridgeContext context = null; + @Override + public RenderSession createSession(SessionParams params) { try { - // setup the display Metrics. - DisplayMetrics metrics = new DisplayMetrics(); - metrics.densityDpi = density; - metrics.density = density / (float) DisplayMetrics.DENSITY_DEFAULT; - metrics.scaledDensity = metrics.density; - metrics.widthPixels = screenWidth; - metrics.heightPixels = screenHeight; - metrics.xdpi = xdpi; - metrics.ydpi = ydpi; - - context = new BridgeContext(projectKey, metrics, currentTheme, projectResources, - frameworkResources, styleParentMap, customViewLoader, logger); - BridgeInflater inflater = new BridgeInflater(context, customViewLoader); - context.setBridgeInflater(inflater); - - IResourceValue windowBackground = null; - int screenOffset = 0; - if (currentTheme != null) { - windowBackground = context.findItemInStyle(currentTheme, "windowBackground"); - windowBackground = context.resolveResValue(windowBackground); - - screenOffset = getScreenOffset(frameworkResources, currentTheme, context); - } - - // we need to make sure the Looper has been initialized for this thread. - // this is required for View that creates Handler objects. - if (Looper.myLooper() == null) { - Looper.prepare(); + Result lastResult = SUCCESS.createResult(); + RenderSessionImpl scene = new RenderSessionImpl(params); + try { + prepareThread(); + lastResult = scene.init(params.getTimeout()); + if (lastResult.isSuccess()) { + lastResult = scene.inflate(); + if (lastResult.isSuccess()) { + lastResult = scene.render(true /*freshRender*/); + } + } + } finally { + scene.release(); + cleanupThread(); } - BridgeXmlBlockParser parser = new BridgeXmlBlockParser(layoutDescription, - context, false /* platformResourceFlag */); - - ViewGroup root = new FrameLayout(context); - - View view = inflater.inflate(parser, root); - - // post-inflate process. For now this supports TabHost/TabWidget - postInflateProcess(view, customViewLoader); - - // set the AttachInfo on the root view. - AttachInfo info = new AttachInfo(new WindowSession(), new Window(), - new Handler(), null); - info.mHasWindowFocus = true; - info.mWindowVisibility = View.VISIBLE; - info.mInTouchMode = false; // this is so that we can display selections. - root.dispatchAttachedToWindow(info, 0); - - // get the background drawable - if (windowBackground != null) { - Drawable d = ResourceHelper.getDrawable(windowBackground, - context, true /* isFramework */); - root.setBackgroundDrawable(d); + return new BridgeRenderSession(scene, lastResult); + } catch (Throwable t) { + // get the real cause of the exception. + Throwable t2 = t; + while (t2.getCause() != null) { + t2 = t.getCause(); } + return new BridgeRenderSession(null, + ERROR_UNKNOWN.createResult(t2.getMessage(), t)); + } + } - // measure the views - int w_spec, h_spec; - - if (renderFullSize) { - // measure the full size needed by the layout. - w_spec = MeasureSpec.makeMeasureSpec(screenWidth, - MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size - h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset, - MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size - view.measure(w_spec, h_spec); - - int neededWidth = root.getChildAt(0).getMeasuredWidth(); - if (neededWidth > screenWidth) { - screenWidth = neededWidth; - } - - int neededHeight = root.getChildAt(0).getMeasuredHeight(); - if (neededHeight > screenHeight - screenOffset) { - screenHeight = neededHeight + screenOffset; + @Override + public Result renderDrawable(DrawableParams params) { + try { + Result lastResult = SUCCESS.createResult(); + RenderDrawable action = new RenderDrawable(params); + try { + prepareThread(); + lastResult = action.init(params.getTimeout()); + if (lastResult.isSuccess()) { + lastResult = action.render(); } + } finally { + action.release(); + cleanupThread(); } - // remeasure with only the size we need - // This must always be done before the call to layout - w_spec = MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY); - h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset, - MeasureSpec.EXACTLY); - view.measure(w_spec, h_spec); - - // now do the layout. - view.layout(0, screenOffset, screenWidth, screenHeight); - - // draw the views - Canvas canvas = new Canvas(screenWidth, screenHeight - screenOffset, logger); - - root.draw(canvas); - canvas.dispose(); - - return new LayoutResult(visit(((ViewGroup)view).getChildAt(0), context), - canvas.getImage()); - } catch (PostInflateException e) { - return new LayoutResult(ILayoutResult.ERROR, "Error during post inflation process:\n" - + e.getMessage()); - } catch (Throwable e) { + return lastResult; + } catch (Throwable t) { // get the real cause of the exception. - Throwable t = e; - while (t.getCause() != null) { - t = t.getCause(); - } - - // log it - logger.error(t); - - // then return with an ERROR status and the message from the real exception - return new LayoutResult(ILayoutResult.ERROR, - t.getClass().getSimpleName() + ": " + t.getMessage()); - } finally { - // Make sure to remove static references, otherwise we could not unload the lib - BridgeResources.clearSystem(); - BridgeAssetManager.clearSystem(); - - // Remove the global logger - synchronized (sDefaultLogger) { - sLogger = sDefaultLogger; + Throwable t2 = t; + while (t2.getCause() != null) { + t2 = t.getCause(); } + return ERROR_UNKNOWN.createResult(t2.getMessage(), t); } } - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutLibBridge#clearCaches(java.lang.Object) - */ + @Override public void clearCaches(Object projectKey) { if (projectKey != null) { sProjectBitmapCache.remove(projectKey); @@ -495,390 +370,104 @@ public final class Bridge implements ILayoutBridge { } /** - * Returns details of a framework resource from its integer value. - * @param value the integer value - * @return an array of 2 strings containing the resource name and type, or null if the id - * does not match any resource. + * Returns the lock for the bridge */ - public static String[] resolveResourceValue(int value) { - return sRMap.get(value); - + public static ReentrantLock getLock() { + return sLock; } /** - * Returns the name of a framework resource whose value is an int array. - * @param array + * Prepares the current thread for rendering. + * + * Note that while this can be called several time, the first call to {@link #cleanupThread()} + * will do the clean-up, and make the thread unable to do further scene actions. */ - public static String resolveResourceValue(int[] array) { - return sRArrayMap.get(array); + public static void prepareThread() { + // we need to make sure the Looper has been initialized for this thread. + // this is required for View that creates Handler objects. + if (Looper.myLooper() == null) { + Looper.prepare(); + } } /** - * Returns the integer id of a framework resource, from a given resource type and resource name. - * @param type the type of the resource - * @param name the name of the resource. - * @return an {@link Integer} containing the resource id, or null if no resource were found. + * Cleans up thread-specific data. After this, the thread cannot be used for scene actions. + * <p> + * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single + * call to this will prevent the thread from doing further scene actions */ - public static Integer getResourceValue(String type, String name) { - Map<String, Integer> map = sRFullMap.get(type); - if (map != null) { - return map.get(name); - } - - return null; + public static void cleanupThread() { + // clean up the looper + Looper.sThreadLocal.remove(); } - static Map<String, Integer> getEnumValues(String attributeName) { - if (sEnumValueMap != null) { - return sEnumValueMap.get(attributeName); - } - - return null; + public static LayoutLog getLog() { + return sCurrentLog; } - /** - * Visits a View and its children and generate a {@link ILayoutViewInfo} containing the - * bounds of all the views. - * @param view the root View - * @param context the context. - */ - private ILayoutViewInfo visit(View view, BridgeContext context) { - if (view == null) { - return null; + public static void setLog(LayoutLog log) { + // check only the thread currently owning the lock can do this. + if (sLock.isHeldByCurrentThread() == false) { + throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); } - LayoutViewInfo result = new LayoutViewInfo(view.getClass().getName(), - context.getViewKey(view), - view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); - - if (view instanceof ViewGroup) { - ViewGroup group = ((ViewGroup) view); - int n = group.getChildCount(); - ILayoutViewInfo[] children = new ILayoutViewInfo[n]; - for (int i = 0; i < group.getChildCount(); i++) { - children[i] = visit(group.getChildAt(i), context); - } - result.setChildren(children); + if (log != null) { + sCurrentLog = log; + } else { + sCurrentLog = sDefaultLog; } - - return result; } /** - * Compute style information from the given list of style for the project and framework. - * @param themeName the name of the current theme. In order to differentiate project and - * platform themes sharing the same name, all project themes must be prepended with - * a '*' character. - * @param isProjectTheme Is this a project theme - * @param inProjectStyleMap the project style map - * @param inFrameworkStyleMap the framework style map - * @param outInheritanceMap the map of style inheritance. This is filled by the method - * @return the {@link IStyleResourceValue} matching <var>themeName</var> + * Returns details of a framework resource from its integer value. + * @param value the integer value + * @return a Pair containing the resource type and name, or null if the id + * does not match any resource. */ - private IStyleResourceValue computeStyleMaps( - String themeName, boolean isProjectTheme, Map<String, - IResourceValue> inProjectStyleMap, Map<String, IResourceValue> inFrameworkStyleMap, - Map<IStyleResourceValue, IStyleResourceValue> outInheritanceMap) { - - if (inProjectStyleMap != null && inFrameworkStyleMap != null) { - // first, get the theme - IResourceValue theme = null; - - // project theme names have been prepended with a * - if (isProjectTheme) { - theme = inProjectStyleMap.get(themeName); - } else { - theme = inFrameworkStyleMap.get(themeName); - } - - if (theme instanceof IStyleResourceValue) { - // compute the inheritance map for both the project and framework styles - computeStyleInheritance(inProjectStyleMap.values(), inProjectStyleMap, - inFrameworkStyleMap, outInheritanceMap); - - // Compute the style inheritance for the framework styles/themes. - // Since, for those, the style parent values do not contain 'android:' - // we want to force looking in the framework style only to avoid using - // similarly named styles from the project. - // To do this, we pass null in lieu of the project style map. - computeStyleInheritance(inFrameworkStyleMap.values(), null /*inProjectStyleMap */, - inFrameworkStyleMap, outInheritanceMap); - - return (IStyleResourceValue)theme; - } - } - - return null; + public static Pair<ResourceType, String> resolveResourceId(int value) { + return sRMap.get(value); } /** - * Compute the parent style for all the styles in a given list. - * @param styles the styles for which we compute the parent. - * @param inProjectStyleMap the map of project styles. - * @param inFrameworkStyleMap the map of framework styles. - * @param outInheritanceMap the map of style inheritance. This is filled by the method. + * Returns the name of a framework resource whose value is an int array. + * @param array */ - private void computeStyleInheritance(Collection<IResourceValue> styles, - Map<String, IResourceValue> inProjectStyleMap, - Map<String, IResourceValue> inFrameworkStyleMap, - Map<IStyleResourceValue, IStyleResourceValue> outInheritanceMap) { - for (IResourceValue value : styles) { - if (value instanceof IStyleResourceValue) { - IStyleResourceValue style = (IStyleResourceValue)value; - IStyleResourceValue parentStyle = null; - - // first look for a specified parent. - String parentName = style.getParentStyle(); - - // no specified parent? try to infer it from the name of the style. - if (parentName == null) { - parentName = getParentName(value.getName()); - } - - if (parentName != null) { - parentStyle = getStyle(parentName, inProjectStyleMap, inFrameworkStyleMap); - - if (parentStyle != null) { - outInheritanceMap.put(style, parentStyle); - } - } - } - } + public static String resolveResourceId(int[] array) { + sIntArrayWrapper.set(array); + return sRArrayMap.get(sIntArrayWrapper); } /** - * Searches for and returns the {@link IStyleResourceValue} from a given name. - * <p/>The format of the name can be: - * <ul> - * <li>[android:]<name></li> - * <li>[android:]style/<name></li> - * <li>@[android:]style/<name></li> - * </ul> - * @param parentName the name of the style. - * @param inProjectStyleMap the project style map. Can be <code>null</code> - * @param inFrameworkStyleMap the framework style map. - * @return The matching {@link IStyleResourceValue} object or <code>null</code> if not found. + * Returns the integer id of a framework resource, from a given resource type and resource name. + * @param type the type of the resource + * @param name the name of the resource. + * @return an {@link Integer} containing the resource id, or null if no resource were found. */ - private IStyleResourceValue getStyle(String parentName, - Map<String, IResourceValue> inProjectStyleMap, - Map<String, IResourceValue> inFrameworkStyleMap) { - boolean frameworkOnly = false; - - String name = parentName; - - // remove the useless @ if it's there - if (name.startsWith(BridgeConstants.PREFIX_RESOURCE_REF)) { - name = name.substring(BridgeConstants.PREFIX_RESOURCE_REF.length()); - } - - // check for framework identifier. - if (name.startsWith(BridgeConstants.PREFIX_ANDROID)) { - frameworkOnly = true; - name = name.substring(BridgeConstants.PREFIX_ANDROID.length()); - } - - // at this point we could have the format <type>/<name>. we want only the name as long as - // the type is style. - if (name.startsWith(BridgeConstants.REFERENCE_STYLE)) { - name = name.substring(BridgeConstants.REFERENCE_STYLE.length()); - } else if (name.indexOf('/') != -1) { - return null; - } - - IResourceValue parent = null; - - // if allowed, search in the project resources. - if (frameworkOnly == false && inProjectStyleMap != null) { - parent = inProjectStyleMap.get(name); - } - - // if not found, then look in the framework resources. - if (parent == null) { - parent = inFrameworkStyleMap.get(name); - } - - // make sure the result is the proper class type and return it. - if (parent instanceof IStyleResourceValue) { - return (IStyleResourceValue)parent; + public static Integer getResourceId(ResourceType type, String name) { + Map<String, Integer> map = sRFullMap.get(type); + if (map != null) { + return map.get(name); } - sLogger.error(String.format("Unable to resolve parent style name: %s", parentName)); - return null; } /** - * Computes the name of the parent style, or <code>null</code> if the style is a root style. + * Returns the list of possible enums for a given attribute name. */ - private String getParentName(String styleName) { - int index = styleName.lastIndexOf('.'); - if (index != -1) { - return styleName.substring(0, index); + public static Map<String, Integer> getEnumValues(String attributeName) { + if (sEnumValueMap != null) { + return sEnumValueMap.get(attributeName); } return null; } /** - * Returns the top screen offset. This depends on whether the current theme defines the user - * of the title and status bars. - * @param frameworkResources The framework resources - * @param currentTheme The current theme - * @param context The context - * @return the pixel height offset - */ - private int getScreenOffset(Map<String, Map<String, IResourceValue>> frameworkResources, - IStyleResourceValue currentTheme, BridgeContext context) { - int offset = 0; - - // get the title bar flag from the current theme. - IResourceValue value = context.findItemInStyle(currentTheme, "windowNoTitle"); - - // because it may reference something else, we resolve it. - value = context.resolveResValue(value); - - // if there's a value and it's true (default is false) - if (value == null || value.getValue() == null || - XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { - // default size of the window title bar - int defaultOffset = DEFAULT_TITLE_BAR_HEIGHT; - - // get value from the theme. - value = context.findItemInStyle(currentTheme, "windowTitleSize"); - - // resolve it - value = context.resolveResValue(value); - - if (value != null) { - // get the numerical value, if available - TypedValue typedValue = ResourceHelper.getValue(value.getValue()); - if (typedValue != null) { - // compute the pixel value based on the display metrics - defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics); - } - } - - offset += defaultOffset; - } - - // get the fullscreen flag from the current theme. - value = context.findItemInStyle(currentTheme, "windowFullscreen"); - - // because it may reference something else, we resolve it. - value = context.resolveResValue(value); - - if (value == null || value.getValue() == null || - XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { - - // default value - int defaultOffset = DEFAULT_STATUS_BAR_HEIGHT; - - // get the real value, first the list of Dimensions from the framework map - Map<String, IResourceValue> dimens = frameworkResources.get(BridgeConstants.RES_DIMEN); - - // now get the value - value = dimens.get("status_bar_height"); - if (value != null) { - TypedValue typedValue = ResourceHelper.getValue(value.getValue()); - if (typedValue != null) { - // compute the pixel value based on the display metrics - defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics); - } - } - - // add the computed offset. - offset += defaultOffset; - } - - return offset; - } - - /** - * Post process on a view hierachy that was just inflated. - * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the - * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically - * based on the content of the {@link FrameLayout}. - * @param view the root view to process. - * @param projectCallback callback to the project. + * Returns the platform build properties. */ - private void postInflateProcess(View view, IProjectCallback projectCallback) - throws PostInflateException { - if (view instanceof TabHost) { - setupTabHost((TabHost)view, projectCallback); - } else if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup)view; - final int count = group.getChildCount(); - for (int c = 0 ; c < count ; c++) { - View child = group.getChildAt(c); - postInflateProcess(child, projectCallback); - } - } - } - - /** - * Sets up a {@link TabHost} object. - * @param tabHost the TabHost to setup. - * @param projectCallback The project callback object to access the project R class. - * @throws PostInflateException - */ - private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback) - throws PostInflateException { - // look for the TabWidget, and the FrameLayout. They have their own specific names - View v = tabHost.findViewById(android.R.id.tabs); - - if (v == null) { - throw new PostInflateException( - "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); - } - - if ((v instanceof TabWidget) == false) { - throw new PostInflateException(String.format( - "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + - "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); - } - - v = tabHost.findViewById(android.R.id.tabcontent); - - if (v == null) { - // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) - throw new PostInflateException( - "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); - } - - if ((v instanceof FrameLayout) == false) { - throw new PostInflateException(String.format( - "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + - "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); - } - - FrameLayout content = (FrameLayout)v; - - // now process the content of the framelayout and dynamically create tabs for it. - final int count = content.getChildCount(); - - if (count == 0) { - throw new PostInflateException( - "The FrameLayout for the TabHost has no content. Rendering failed.\n"); - } - - // this must be called before addTab() so that the TabHost searches its TabWidget - // and FrameLayout. - tabHost.setup(); - - // for each child of the framelayout, add a new TabSpec - for (int i = 0 ; i < count ; i++) { - View child = content.getChildAt(i); - String tabSpec = String.format("tab_spec%d", i+1); - int id = child.getId(); - String[] resource = projectCallback.resolveResourceValue(id); - String name; - if (resource != null) { - name = resource[0]; // 0 is resource name, 1 is resource type. - } else { - name = String.format("Tab %d", i+1); // default name if id is unresolved. - } - tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id)); - } + public static Map<String, String> getPlatformProperties() { + return sPlatformProperties; } /** @@ -888,7 +477,7 @@ public final class Bridge implements ILayoutBridge { * @param projectKey the key of the project, or null to query the framework cache. * @return the cached Bitmap or null if not found. */ - static Bitmap getCachedBitmap(String value, Object projectKey) { + public static Bitmap getCachedBitmap(String value, Object projectKey) { if (projectKey != null) { Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); if (map != null) { @@ -913,7 +502,7 @@ public final class Bridge implements ILayoutBridge { * @param bmp the Bitmap object * @param projectKey the key of the project, or null to put the bitmap in the framework cache. */ - static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { + public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { if (projectKey != null) { Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); @@ -929,24 +518,24 @@ public final class Bridge implements ILayoutBridge { } /** - * Returns the 9 patch for a specific path, from a specific project cache, or from the + * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the * framework cache. * @param value the path of the 9 patch * @param projectKey the key of the project, or null to query the framework cache. * @return the cached 9 patch or null if not found. */ - static NinePatch getCached9Patch(String value, Object projectKey) { + public static NinePatchChunk getCached9Patch(String value, Object projectKey) { if (projectKey != null) { - Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); + Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); if (map != null) { - SoftReference<NinePatch> ref = map.get(value); + SoftReference<NinePatchChunk> ref = map.get(value); if (ref != null) { return ref.get(); } } } else { - SoftReference<NinePatch> ref = sFramework9PatchCache.get(value); + SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value); if (ref != null) { return ref.get(); } @@ -956,224 +545,25 @@ public final class Bridge implements ILayoutBridge { } /** - * Sets a 9 patch in a project cache or in the framework cache. + * Sets a 9 patch chunk in a project cache or in the framework cache. * @param value the path of the 9 patch * @param ninePatch the 9 patch object * @param projectKey the key of the project, or null to put the bitmap in the framework cache. */ - static void setCached9Patch(String value, NinePatch ninePatch, Object projectKey) { + public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) { if (projectKey != null) { - Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); + Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); if (map == null) { - map = new HashMap<String, SoftReference<NinePatch>>(); + map = new HashMap<String, SoftReference<NinePatchChunk>>(); sProject9PatchCache.put(projectKey, map); } - map.put(value, new SoftReference<NinePatch>(ninePatch)); + map.put(value, new SoftReference<NinePatchChunk>(ninePatch)); } else { - sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch)); + sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch)); } } - private static final class PostInflateException extends Exception { - private static final long serialVersionUID = 1L; - - public PostInflateException(String message) { - super(message); - } - } - - /** - * Implementation of {@link IWindowSession} so that mSession is not null in - * the {@link SurfaceView}. - */ - private static final class WindowSession implements IWindowSession { - - @SuppressWarnings("unused") - public int add(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3, - InputChannel outInputchannel) - throws RemoteException { - // pass for now. - return 0; - } - - @SuppressWarnings("unused") - public int addWithoutInputChannel(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3) - throws RemoteException { - // pass for now. - return 0; - } - - @SuppressWarnings("unused") - public void finishDrawing(IWindow arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void finishKey(IWindow arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public boolean getInTouchMode() throws RemoteException { - // pass for now. - return false; - } - - @SuppressWarnings("unused") - public boolean performHapticFeedback(IWindow window, int effectId, boolean always) { - // pass for now. - return false; - } - - @SuppressWarnings("unused") - public MotionEvent getPendingPointerMove(IWindow arg0) throws RemoteException { - // pass for now. - return null; - } - - @SuppressWarnings("unused") - public MotionEvent getPendingTrackballMove(IWindow arg0) throws RemoteException { - // pass for now. - return null; - } - - @SuppressWarnings("unused") - public int relayout(IWindow arg0, LayoutParams arg1, int arg2, int arg3, int arg4, - boolean arg4_5, Rect arg5, Rect arg6, Rect arg7, Configuration arg7b, Surface arg8) - throws RemoteException { - // pass for now. - return 0; - } - - public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { - // pass for now. - } - - @SuppressWarnings("unused") - public void remove(IWindow arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void setInTouchMode(boolean arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void setInsets(IWindow window, int touchable, Rect contentInsets, - Rect visibleInsets) { - // pass for now. - } - - @SuppressWarnings("unused") - public void setWallpaperPosition(IBinder window, float x, float y, - float xStep, float yStep) { - // pass for now. - } - - @SuppressWarnings("unused") - public void wallpaperOffsetsComplete(IBinder window) { - // pass for now. - } - - @SuppressWarnings("unused") - public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, - int z, Bundle extras, boolean sync) { - // pass for now. - return null; - } - - @SuppressWarnings("unused") - public void wallpaperCommandComplete(IBinder window, Bundle result) { - // pass for now. - } - - @SuppressWarnings("unused") - public void closeSystemDialogs(String reason) { - // pass for now. - } - - public IBinder asBinder() { - // pass for now. - return null; - } - } - - /** - * Implementation of {@link IWindow} to pass to the {@link AttachInfo}. - */ - private static final class Window implements IWindow { - - @SuppressWarnings("unused") - public void dispatchAppVisibility(boolean arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchGetNewSurface() throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchKey(KeyEvent arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchPointer(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchTrackball(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2) - throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void resized(int arg0, int arg1, Rect arg2, Rect arg3, boolean arg4, Configuration arg5) - throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, - boolean sync) { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchWallpaperCommand(String action, int x, int y, - int z, Bundle extras, boolean sync) { - // pass for now. - } - - @SuppressWarnings("unused") - public void closeSystemDialogs(String reason) { - // pass for now. - } - - public IBinder asBinder() { - // pass for now. - return null; - } - } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java index 791e53b..112af1e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java @@ -41,24 +41,6 @@ public class BridgeConstants { public final static String R = "com.android.internal.R"; - public final static String PREFIX_ANDROID_RESOURCE_REF = "@android:"; - public final static String PREFIX_RESOURCE_REF = "@"; - public final static String PREFIX_ANDROID_THEME_REF = "?android:"; - public final static String PREFIX_THEME_REF = "?"; - - public final static String PREFIX_ANDROID = "android:"; - - public final static String RES_STYLE = "style"; - public final static String RES_ATTR = "attr"; - public final static String RES_DIMEN = "dimen"; - public final static String RES_DRAWABLE = "drawable"; - public final static String RES_COLOR = "color"; - public final static String RES_LAYOUT = "layout"; - public final static String RES_STRING = "string"; - public final static String RES_ID = "id"; - - public final static String REFERENCE_STYLE = RES_STYLE + "/"; - public final static String REFERENCE_NULL = "@null"; public final static String MATCH_PARENT = "match_parent"; public final static String FILL_PARENT = "fill_parent"; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java deleted file mode 100644 index 20ccc0b..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2009 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.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.IContentProvider; -import android.content.OperationApplicationException; -import android.content.res.AssetFileDescriptor; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.CursorWindow; -import android.database.IBulkCursor; -import android.database.IContentObserver; -import android.net.Uri; -import android.os.Bundle; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; - -import java.io.FileNotFoundException; -import java.util.ArrayList; - -/** - * A mock content resolver for the LayoutLib Bridge. - * <p/> - * It won't serve any actual data but it's good enough for all - * the widgets which expect to have a content resolver available via - * {@link BridgeContext#getContentResolver()}. - */ -public class BridgeContentResolver extends ContentResolver { - - private BridgeContentProvider mProvider = null; - - public static final class BridgeContentProvider implements IContentProvider { - - public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> arg0) - throws RemoteException, OperationApplicationException { - // TODO Auto-generated method stub - return null; - } - - public int bulkInsert(Uri arg0, ContentValues[] arg1) throws RemoteException { - // TODO Auto-generated method stub - return 0; - } - - public IBulkCursor bulkQuery(Uri arg0, String[] arg1, String arg2, String[] arg3, - String arg4, IContentObserver arg5, CursorWindow arg6) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - public Bundle call(String arg0, String arg1, Bundle arg2) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - public int delete(Uri arg0, String arg1, String[] arg2) throws RemoteException { - // TODO Auto-generated method stub - return 0; - } - - public String getType(Uri arg0) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - public Uri insert(Uri arg0, ContentValues arg1) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - public AssetFileDescriptor openAssetFile(Uri arg0, String arg1) throws RemoteException, - FileNotFoundException { - // TODO Auto-generated method stub - return null; - } - - public ParcelFileDescriptor openFile(Uri arg0, String arg1) throws RemoteException, - FileNotFoundException { - // TODO Auto-generated method stub - return null; - } - - public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) - throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) - throws RemoteException { - // TODO Auto-generated method stub - return 0; - } - - public IBinder asBinder() { - // TODO Auto-generated method stub - return null; - } - - } - - public BridgeContentResolver(Context context) { - super(context); - } - - @Override - public IContentProvider acquireProvider(Context c, String name) { - if (mProvider == null) { - mProvider = new BridgeContentProvider(); - } - - return mProvider; - } - - @Override - public IContentProvider acquireExistingProvider(Context c, String name) { - if (mProvider == null) { - mProvider = new BridgeContentProvider(); - } - - return mProvider; - } - - @Override - public boolean releaseProvider(IContentProvider icp) { - // ignore - return false; - } - - /** - * Stub for the layoutlib bridge content resolver. - */ - @Override - public void registerContentObserver(Uri uri, boolean notifyForDescendents, - ContentObserver observer) { - // pass - } - - /** - * Stub for the layoutlib bridge content resolver. - */ - @Override - public void unregisterContentObserver(ContentObserver observer) { - // pass - } - - /** - * Stub for the layoutlib bridge content resolver. - */ - @Override - public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { - // pass - } - - /** - * Stub for the layoutlib bridge content resolver. - */ - @Override - public void startSync(Uri uri, Bundle extras) { - // pass - } - - /** - * Stub for the layoutlib bridge content resolver. - */ - @Override - public void cancelSync(Uri uri) { - // pass - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java new file mode 100644 index 0000000..765fd99 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -0,0 +1,223 @@ +/* + * 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.ide.common.rendering.api.IAnimationListener; +import com.android.ide.common.rendering.api.ILayoutPullParser; +import com.android.ide.common.rendering.api.RenderParams; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.Result.Status; +import com.android.layoutlib.bridge.impl.RenderSessionImpl; + +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +import java.awt.image.BufferedImage; +import java.util.List; +import java.util.Map; + +/** + * An implementation of {@link RenderSession}. + * + * This is a pretty basic class that does almost nothing. All of the work is done in + * {@link RenderSessionImpl}. + * + */ +public class BridgeRenderSession extends RenderSession { + + private final RenderSessionImpl mSession; + private Result mLastResult; + + @Override + public Result getResult() { + return mLastResult; + } + + @Override + public BufferedImage getImage() { + return mSession.getImage(); + } + + @Override + public boolean isAlphaChannelImage() { + return mSession.isAlphaChannelImage(); + } + + @Override + public List<ViewInfo> getRootViews() { + return mSession.getViewInfos(); + } + + @Override + public Map<String, String> getDefaultProperties(Object viewObject) { + return mSession.getDefaultProperties(viewObject); + } + + @Override + public Result getProperty(Object objectView, String propertyName) { + // TODO Auto-generated method stub + return super.getProperty(objectView, propertyName); + } + + @Override + public Result setProperty(Object objectView, String propertyName, String propertyValue) { + // TODO Auto-generated method stub + return super.setProperty(objectView, propertyName, propertyValue); + } + + @Override + public Result getViewParent(Object viewObject) { + if (viewObject instanceof View) { + return Status.SUCCESS.createResult(((View)viewObject).getParent()); + } + + throw new IllegalArgumentException("viewObject is not a View"); + } + + @Override + public Result getViewIndex(Object viewObject) { + if (viewObject instanceof View) { + View view = (View) viewObject; + ViewParent parentView = view.getParent(); + + if (parentView instanceof ViewGroup) { + Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view)); + } + + return Status.SUCCESS.createResult(); + } + + throw new IllegalArgumentException("viewObject is not a View"); + } + + @Override + public Result render(long timeout) { + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(timeout); + if (mLastResult.isSuccess()) { + mLastResult = mSession.render(false /*freshRender*/); + } + } finally { + mSession.release(); + Bridge.cleanupThread(); + } + + return mLastResult; + } + + @Override + public Result animate(Object targetObject, String animationName, + boolean isFrameworkAnimation, IAnimationListener listener) { + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT); + if (mLastResult.isSuccess()) { + mLastResult = mSession.animate(targetObject, animationName, isFrameworkAnimation, + listener); + } + } finally { + mSession.release(); + Bridge.cleanupThread(); + } + + return mLastResult; + } + + @Override + public Result insertChild(Object parentView, ILayoutPullParser childXml, int index, + IAnimationListener listener) { + if (parentView instanceof ViewGroup == false) { + throw new IllegalArgumentException("parentView is not a ViewGroup"); + } + + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT); + if (mLastResult.isSuccess()) { + mLastResult = mSession.insertChild((ViewGroup) parentView, childXml, index, + listener); + } + } finally { + mSession.release(); + Bridge.cleanupThread(); + } + + return mLastResult; + } + + + @Override + public Result moveChild(Object parentView, Object childView, int index, + Map<String, String> layoutParams, IAnimationListener listener) { + if (parentView instanceof ViewGroup == false) { + throw new IllegalArgumentException("parentView is not a ViewGroup"); + } + if (childView instanceof View == false) { + throw new IllegalArgumentException("childView is not a View"); + } + + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT); + if (mLastResult.isSuccess()) { + mLastResult = mSession.moveChild((ViewGroup) parentView, (View) childView, index, + layoutParams, listener); + } + } finally { + mSession.release(); + Bridge.cleanupThread(); + } + + return mLastResult; + } + + @Override + public Result removeChild(Object childView, IAnimationListener listener) { + if (childView instanceof View == false) { + throw new IllegalArgumentException("childView is not a View"); + } + + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT); + if (mLastResult.isSuccess()) { + mLastResult = mSession.removeChild((View) childView, listener); + } + } finally { + mSession.release(); + Bridge.cleanupThread(); + } + + return mLastResult; + } + + @Override + public void dispose() { + } + + /*package*/ BridgeRenderSession(RenderSessionImpl scene, Result lastResult) { + mSession = scene; + if (scene != null) { + mSession.setScene(this); + } + mLastResult = lastResult; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/LayoutResult.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/LayoutResult.java deleted file mode 100644 index c4c5225..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/LayoutResult.java +++ /dev/null @@ -1,126 +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 com.android.layoutlib.bridge; - -import com.android.layoutlib.api.ILayoutResult; - -import java.awt.image.BufferedImage; - -/** - * Implementation of {@link ILayoutResult} - */ -public final class LayoutResult implements ILayoutResult { - - private final ILayoutViewInfo mRootView; - private final BufferedImage mImage; - private final int mSuccess; - private final String mErrorMessage; - - /** - * Creates a {@link #SUCCESS} {@link ILayoutResult} with the specified params - * @param rootView - * @param image - */ - public LayoutResult(ILayoutViewInfo rootView, BufferedImage image) { - mSuccess = SUCCESS; - mErrorMessage = null; - mRootView = rootView; - mImage = image; - } - - /** - * Creates a LayoutResult with a specific success code and associated message - * @param code - * @param message - */ - public LayoutResult(int code, String message) { - mSuccess = code; - mErrorMessage = message; - mRootView = null; - mImage = null; - } - - public int getSuccess() { - return mSuccess; - } - - public String getErrorMessage() { - return mErrorMessage; - } - - public BufferedImage getImage() { - return mImage; - } - - public ILayoutViewInfo getRootView() { - return mRootView; - } - - /** - * Implementation of {@link ILayoutResult.ILayoutViewInfo} - */ - public static final class LayoutViewInfo implements ILayoutViewInfo { - private final Object mKey; - private final String mName; - private final int mLeft; - private final int mRight; - private final int mTop; - private final int mBottom; - private ILayoutViewInfo[] mChildren; - - public LayoutViewInfo(String name, Object key, int left, int top, int right, int bottom) { - mName = name; - mKey = key; - mLeft = left; - mRight = right; - mTop = top; - mBottom = bottom; - } - - public void setChildren(ILayoutViewInfo[] children) { - mChildren = children; - } - - public ILayoutViewInfo[] getChildren() { - return mChildren; - } - - public Object getViewKey() { - return mKey; - } - - public String getName() { - return mName; - } - - public int getLeft() { - return mLeft; - } - - public int getTop() { - return mTop; - } - - public int getRight() { - return mRight; - } - - public int getBottom() { - return mBottom; - } - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/NinePatchDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/NinePatchDrawable.java deleted file mode 100644 index abbf2f0..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/NinePatchDrawable.java +++ /dev/null @@ -1,107 +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 com.android.layoutlib.bridge; - -import com.android.ninepatch.NinePatch; - -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; - -public class NinePatchDrawable extends Drawable { - - private NinePatch m9Patch; - - NinePatchDrawable(NinePatch ninePatch) { - m9Patch = ninePatch; - } - - @Override - public int getMinimumWidth() { - return m9Patch.getWidth(); - } - - @Override - public int getMinimumHeight() { - return m9Patch.getHeight(); - } - - /** - * Return the intrinsic width of the underlying drawable object. Returns - * -1 if it has no intrinsic width, such as with a solid color. - */ - @Override - public int getIntrinsicWidth() { - return m9Patch.getWidth(); - } - - /** - * Return the intrinsic height of the underlying drawable object. Returns - * -1 if it has no intrinsic height, such as with a solid color. - */ - @Override - public int getIntrinsicHeight() { - return m9Patch.getHeight(); - } - - /** - * Return in padding the insets suggested by this Drawable for placing - * content inside the drawable's bounds. Positive values move toward the - * center of the Drawable (set Rect.inset). Returns true if this drawable - * actually has a padding, else false. When false is returned, the padding - * is always set to 0. - */ - @Override - public boolean getPadding(Rect padding) { - int[] padd = new int[4]; - m9Patch.getPadding(padd); - padding.left = padd[0]; - padding.top = padd[1]; - padding.right = padd[2]; - padding.bottom = padd[3]; - return true; - } - - @Override - public void draw(Canvas canvas) { - Rect r = getBounds(); - m9Patch.draw(canvas.getGraphics2d(), r.left, r.top, r.width(), r.height()); - - return; - } - - - // ----------- Not implemented methods --------------- - - - @Override - public int getOpacity() { - // FIXME - return 0xFF; - } - - @Override - public void setAlpha(int arg0) { - // FIXME ! - } - - @Override - public void setColorFilter(ColorFilter arg0) { - // FIXME - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceValue.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceValue.java deleted file mode 100644 index 01a4871..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceValue.java +++ /dev/null @@ -1,67 +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 com.android.layoutlib.bridge; - -import com.android.layoutlib.api.IResourceValue; - -/** - * Basic implementation of IResourceValue. - */ -class ResourceValue implements IResourceValue { - private final String mType; - private final String mName; - private String mValue = null; - - ResourceValue(String name) { - mType = null; - mName = name; - } - - public ResourceValue(String type, String name, String value) { - mType = type; - mName = name; - mValue = value; - } - - public String getType() { - return mType; - } - - public final String getName() { - return mName; - } - - public final String getValue() { - return mValue; - } - - public final void setValue(String value) { - mValue = value; - } - - public void replaceWith(ResourceValue value) { - mValue = value.mValue; - } - - public boolean isFramework() { - // ResourceValue object created directly in the framework are used to describe - // non resolvable coming from the XML. Since they will never be cached (as they can't - // be a value pointing to a bitmap, or they'd be resolvable.), the return value deoes - // not matter. - return false; - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeAssetManager.java index 71803fc..a825060 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeAssetManager.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.android; + +import com.android.layoutlib.bridge.Bridge; import android.content.res.AssetManager; @@ -28,7 +30,7 @@ public class BridgeAssetManager extends AssetManager { * <p/> * {@link Bridge} calls this method after setting up a new bridge. */ - /*package*/ static AssetManager initSystem() { + /*package*/ public static AssetManager initSystem() { if (!(AssetManager.sSystem instanceof BridgeAssetManager)) { // Note that AssetManager() creates a system AssetManager and we override it // with our BridgeAssetManager. @@ -42,7 +44,7 @@ public class BridgeAssetManager extends AssetManager { * Clears the static {@link AssetManager#sSystem} to make sure we don't leave objects * around that would prevent us from unloading the library. */ - /*package*/ static void clearSystem() { + public static void clearSystem() { AssetManager.sSystem = null; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java new file mode 100644 index 0000000..3835378 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java @@ -0,0 +1,122 @@ +/* + * 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.android; + +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentValues; +import android.content.IContentProvider; +import android.content.OperationApplicationException; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.database.CursorWindow; +import android.database.IBulkCursor; +import android.database.IContentObserver; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import java.io.FileNotFoundException; +import java.util.ArrayList; + +/** + * Mock implementation of {@link IContentProvider}. + * + * TODO: never return null when the method is not supposed to. Return fake data instead. + */ +public final class BridgeContentProvider implements IContentProvider { + + public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> arg0) + throws RemoteException, OperationApplicationException { + // TODO Auto-generated method stub + return null; + } + + public int bulkInsert(Uri arg0, ContentValues[] arg1) throws RemoteException { + // TODO Auto-generated method stub + return 0; + } + + public IBulkCursor bulkQuery(Uri arg0, String[] arg1, String arg2, String[] arg3, + String arg4, IContentObserver arg5, CursorWindow arg6) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public Bundle call(String arg0, String arg1, Bundle arg2) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public int delete(Uri arg0, String arg1, String[] arg2) throws RemoteException { + // TODO Auto-generated method stub + return 0; + } + + public String getType(Uri arg0) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public Uri insert(Uri arg0, ContentValues arg1) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public AssetFileDescriptor openAssetFile(Uri arg0, String arg1) throws RemoteException, + FileNotFoundException { + // TODO Auto-generated method stub + return null; + } + + public ParcelFileDescriptor openFile(Uri arg0, String arg1) throws RemoteException, + FileNotFoundException { + // TODO Auto-generated method stub + return null; + } + + public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) + throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) + throws RemoteException { + // TODO Auto-generated method stub + return 0; + } + + public IBinder asBinder() { + // TODO Auto-generated method stub + 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; + } + +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java new file mode 100644 index 0000000..0257686 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2009 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.android; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.IContentProvider; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; + +/** + * A mock content resolver for the LayoutLib Bridge. + * <p/> + * It won't serve any actual data but it's good enough for all + * the widgets which expect to have a content resolver available via + * {@link BridgeContext#getContentResolver()}. + */ +public class BridgeContentResolver extends ContentResolver { + + private BridgeContentProvider mProvider = null; + + public BridgeContentResolver(Context context) { + super(context); + } + + @Override + public IContentProvider acquireProvider(Context c, String name) { + if (mProvider == null) { + mProvider = new BridgeContentProvider(); + } + + return mProvider; + } + + @Override + public IContentProvider acquireExistingProvider(Context c, String name) { + if (mProvider == null) { + mProvider = new BridgeContentProvider(); + } + + return mProvider; + } + + @Override + public boolean releaseProvider(IContentProvider icp) { + // ignore + return false; + } + + /** + * Stub for the layoutlib bridge content resolver. + */ + @Override + public void registerContentObserver(Uri uri, boolean notifyForDescendents, + ContentObserver observer) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + */ + @Override + public void unregisterContentObserver(ContentObserver observer) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + */ + @Override + public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + */ + @Override + public void startSync(Uri uri, Bundle extras) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + */ + @Override + public void cancelSync(Uri uri) { + // pass + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index ecd22e2..7d794bd 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -14,13 +14,21 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; - -import com.android.layoutlib.api.ILayoutLog; -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IStyleResourceValue; - +package com.android.layoutlib.bridge.android; + +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.impl.Stack; +import com.android.resources.ResourceType; +import com.android.util.Pair; + +import android.app.Activity; +import android.app.Fragment; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -37,6 +45,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; @@ -47,7 +56,8 @@ import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.view.BridgeInflater; +import android.util.TypedValue; +import android.view.LayoutInflater; import android.view.View; import java.io.File; @@ -57,81 +67,101 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.Map; import java.util.TreeMap; import java.util.Map.Entry; /** - * Custom implementation of Context to handle non compiled resources. + * Custom implementation of Context/Activity to handle non compiled resources. */ -public final class BridgeContext extends Context { +public final class BridgeContext extends Activity { - private final Resources mResources; - private final Theme mTheme; + private Resources mSystemResources; private final HashMap<View, Object> mViewKeyMap = new HashMap<View, Object>(); - private final IStyleResourceValue mThemeValues; private final Object mProjectKey; - private final Map<String, Map<String, IResourceValue>> mProjectResources; - private final Map<String, Map<String, IResourceValue>> mFrameworkResources; - private final Map<IStyleResourceValue, IStyleResourceValue> mStyleInheritanceMap; + private final DisplayMetrics mMetrics; + private final RenderResources mRenderResources; + private final ApplicationInfo mApplicationInfo; + + private final Map<Object, Map<String, String>> mDefaultPropMaps = + new IdentityHashMap<Object, Map<String,String>>(); - // maps for dynamically generated id representing style objects (IStyleResourceValue) - private Map<Integer, IStyleResourceValue> mDynamicIdToStyleMap; - private Map<IStyleResourceValue, Integer> mStyleToDynamicIdMap; + // maps for dynamically generated id representing style objects (StyleResourceValue) + private Map<Integer, StyleResourceValue> mDynamicIdToStyleMap; + private Map<StyleResourceValue, Integer> mStyleToDynamicIdMap; private int mDynamicIdGenerator = 0x01030000; // Base id for framework R.style // cache for TypedArray generated from IStyleResourceValue object private Map<int[], Map<Integer, TypedArray>> mTypedArrayCache; - private BridgeInflater mInflater; + private BridgeInflater mBridgeInflater; private final IProjectCallback mProjectCallback; - private final ILayoutLog mLogger; private BridgeContentResolver mContentResolver; + private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>(); + /** * @param projectKey An Object identifying the project. This is used for the cache mechanism. * @param metrics the {@link DisplayMetrics}. * @param themeName The name of the theme to use. * @param projectResources the resources of the project. The map contains (String, map) pairs * where the string is the type of the resource reference used in the layout file, and the - * map contains (String, {@link IResourceValue}) pairs where the key is the resource name, + * map contains (String, {@link }) pairs where the key is the resource name, * and the value is the resource value. * @param frameworkResources the framework resources. The map contains (String, map) pairs * where the string is the type of the resource reference used in the layout file, and the map - * contains (String, {@link IResourceValue}) pairs where the key is the resource name, and the + * contains (String, {@link ResourceValue}) pairs where the key is the resource name, and the * value is the resource value. * @param styleInheritanceMap - * @param customViewLoader + * @param projectCallback + * @param targetSdkVersion the targetSdkVersion of the application. */ public BridgeContext(Object projectKey, DisplayMetrics metrics, - IStyleResourceValue currentTheme, - Map<String, Map<String, IResourceValue>> projectResources, - Map<String, Map<String, IResourceValue>> frameworkResources, - Map<IStyleResourceValue, IStyleResourceValue> styleInheritanceMap, - IProjectCallback customViewLoader, ILayoutLog logger) { + RenderResources renderResources, + IProjectCallback projectCallback, + int targetSdkVersion) { mProjectKey = projectKey; - mProjectCallback = customViewLoader; - mLogger = logger; + mMetrics = metrics; + mProjectCallback = projectCallback; + + mRenderResources = renderResources; + + mFragments.mCurState = Fragment.CREATED; + mFragments.mActivity = this; + + mApplicationInfo = new ApplicationInfo(); + mApplicationInfo.targetSdkVersion = targetSdkVersion; + } + + /** + * Initializes the {@link Resources} singleton to be linked to this {@link Context}, its + * {@link DisplayMetrics}, {@link Configuration}, and {@link IProjectCallback}. + * + * @see #disposeResources() + */ + public void initResources() { + AssetManager assetManager = AssetManager.getSystem(); Configuration config = new Configuration(); - AssetManager assetManager = BridgeAssetManager.initSystem(); - mResources = BridgeResources.initSystem( + mSystemResources = BridgeResources.initSystem( this, assetManager, - metrics, + mMetrics, config, - customViewLoader); - - mTheme = mResources.newTheme(); + mProjectCallback); + mTheme = mSystemResources.newTheme(); + } - mThemeValues = currentTheme; - mProjectResources = projectResources; - mFrameworkResources = frameworkResources; - mStyleInheritanceMap = styleInheritanceMap; + /** + * Disposes the {@link Resources} singleton. + */ + public void disposeResources() { + BridgeResources.disposeSystem(); } public void setBridgeInflater(BridgeInflater inflater) { - mInflater = inflater; + mBridgeInflater = inflater; } public void addViewKey(View view, Object viewKey) { @@ -146,19 +176,112 @@ public final class BridgeContext extends Context { return mProjectKey; } + public DisplayMetrics getMetrics() { + return mMetrics; + } + public IProjectCallback getProjectCallback() { return mProjectCallback; } - public ILayoutLog getLogger() { - return mLogger; + public RenderResources getRenderResources() { + return mRenderResources; + } + + public Map<String, String> getDefaultPropMap(Object key) { + return mDefaultPropMaps.get(key); + } + + /** + * Adds a parser to the stack. + * @param parser the parser to add. + */ + public void pushParser(BridgeXmlBlockParser parser) { + mParserStack.push(parser); + } + + /** + * Removes the parser at the top of the stack + */ + public void popParser() { + mParserStack.pop(); + } + + /** + * Returns the current parser at the top the of the stack. + * @return a parser or null. + */ + public BridgeXmlBlockParser getCurrentParser() { + return mParserStack.peek(); + } + + /** + * Returns the previous parser. + * @return a parser or null if there isn't any previous parser + */ + public BridgeXmlBlockParser getPreviousParser() { + if (mParserStack.size() < 2) { + return null; + } + return mParserStack.get(mParserStack.size() - 2); + } + + public boolean resolveThemeAttribute(int resid, TypedValue outValue, boolean resolveRefs) { + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(resid); + if (resourceInfo == null) { + resourceInfo = mProjectCallback.resolveResourceId(resid); + } + + if (resourceInfo == null) { + return false; + } + + ResourceValue value = mRenderResources.findItemInTheme(resourceInfo.getSecond()); + if (resolveRefs) { + value = mRenderResources.resolveResValue(value); + } + + // check if this is a style resource + if (value instanceof StyleResourceValue) { + // get the id that will represent this style. + outValue.resourceId = getDynamicIdByStyle((StyleResourceValue)value); + return true; + } + + + int a; + // if this is a framework value. + if (value.isFramework()) { + // look for idName in the android R classes. + // use 0 a default res value as it's not a valid id value. + a = getFrameworkResourceValue(value.getResourceType(), value.getName(), 0 /*defValue*/); + } else { + // look for idName in the project R class. + // use 0 a default res value as it's not a valid id value. + a = getProjectResourceValue(value.getResourceType(), value.getName(), 0 /*defValue*/); + } + + if (a != 0) { + outValue.resourceId = a; + return true; + } + + return false; + } + + + // ------------- Activity Methods + + @Override + public LayoutInflater getLayoutInflater() { + return mBridgeInflater; } // ------------ Context methods @Override public Resources getResources() { - return mResources; + return mSystemResources; } @Override @@ -174,7 +297,7 @@ public final class BridgeContext extends Context { @Override public Object getSystemService(String service) { if (LAYOUT_INFLATER_SERVICE.equals(service)) { - return mInflater; + return mBridgeInflater; } // AutoCompleteTextView and MultiAutoCompleteTextView want a window @@ -183,20 +306,25 @@ public final class BridgeContext extends Context { return null; } + // needed by SearchView + if (INPUT_METHOD_SERVICE.equals(service)) { + return null; + } + throw new UnsupportedOperationException("Unsupported Service: " + service); } @Override public final TypedArray obtainStyledAttributes(int[] attrs) { - return createStyleBasedTypedArray(mThemeValues, attrs); + return createStyleBasedTypedArray(mRenderResources.getCurrentTheme(), attrs); } @Override public final TypedArray obtainStyledAttributes(int resid, int[] attrs) throws Resources.NotFoundException { - // get the IStyleResourceValue based on the resId; - IStyleResourceValue style = getStyleByDynamicId(resid); + // get the StyleResourceValue based on the resId; + StyleResourceValue style = getStyleByDynamicId(resid); if (style == null) { throw new Resources.NotFoundException(); @@ -241,63 +369,128 @@ public final class BridgeContext extends Context { public TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { + Map<String, String> defaultPropMap = null; + boolean isPlatformFile = true; + // Hint: for XmlPullParser, attach source //DEVICE_SRC/dalvik/libcore/xml/src/java - BridgeXmlBlockParser parser = null; if (set instanceof BridgeXmlBlockParser) { + BridgeXmlBlockParser parser = null; parser = (BridgeXmlBlockParser)set; + + isPlatformFile = parser.isPlatformFile(); + + Object key = parser.getViewCookie(); + if (key != null) { + defaultPropMap = mDefaultPropMaps.get(key); + if (defaultPropMap == null) { + defaultPropMap = new HashMap<String, String>(); + mDefaultPropMaps.put(key, defaultPropMap); + } + } + + } else if (set instanceof BridgeLayoutParamsMapAttributes) { + // this is only for temp layout params generated dynamically, so this is never + // platform content. + isPlatformFile = false; } else if (set != null) { // null parser is ok // really this should not be happening since its instantiated in Bridge - mLogger.error("Parser is not a BridgeXmlBlockParser!"); + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Parser is not a BridgeXmlBlockParser!", null /*data*/); return null; } boolean[] frameworkAttributes = new boolean[1]; TreeMap<Integer, String> styleNameMap = searchAttrs(attrs, frameworkAttributes); - BridgeTypedArray ta = ((BridgeResources) mResources).newTypeArray(attrs.length, - parser != null ? parser.isPlatformFile() : true); + BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length, + isPlatformFile); // resolve the defStyleAttr value into a IStyleResourceValue - IStyleResourceValue defStyleValues = null; + StyleResourceValue defStyleValues = null; // look for a custom style. String customStyle = null; - if (parser != null) { - customStyle = parser.getAttributeValue(null /* namespace*/, "style"); + if (set != null) { + customStyle = set.getAttributeValue(null /* namespace*/, "style"); } if (customStyle != null) { - IResourceValue item = findResValue(customStyle, false /*forceFrameworkOnly*/); + ResourceValue item = mRenderResources.findResValue(customStyle, + false /*forceFrameworkOnly*/); + + // resolve it in case it links to something else + item = mRenderResources.resolveResValue(item); - if (item instanceof IStyleResourceValue) { - defStyleValues = (IStyleResourceValue)item; + if (item instanceof StyleResourceValue) { + defStyleValues = (StyleResourceValue)item; } } - if (defStyleValues == null && defStyleAttr != 0) { - // get the name from the int. - String defStyleName = searchAttr(defStyleAttr); + if (defStyleValues == null) { + if (defStyleAttr != 0) { + // get the name from the int. + String defStyleName = searchAttr(defStyleAttr); - // look for the style in the current theme, and its parent: - if (mThemeValues != null) { - IResourceValue item = findItemInStyle(mThemeValues, defStyleName); + if (defaultPropMap != null) { + defaultPropMap.put("style", defStyleName); + } + + // look for the style in the current theme, and its parent: + ResourceValue item = mRenderResources.findItemInTheme(defStyleName); if (item != null) { // item is a reference to a style entry. Search for it. - item = findResValue(item.getValue(), false /*forceFrameworkOnly*/); + item = mRenderResources.findResValue(item.getValue(), + false /*forceFrameworkOnly*/); - if (item instanceof IStyleResourceValue) { - defStyleValues = (IStyleResourceValue)item; + if (item instanceof StyleResourceValue) { + defStyleValues = (StyleResourceValue)item; } } else { - // TODO: log the error properly - System.out.println("Failed to find defStyle: " + defStyleName); + Bridge.getLog().error(null, + String.format( + "Failed to find style '%s' in current theme", defStyleName), + null /*data*/); + } + } else if (defStyleRes != 0) { + Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes); + if (value == null) { + value = mProjectCallback.resolveResourceId(defStyleRes); } - } - } - if (defStyleRes != 0) { - // FIXME: See what we need to do with this. - throw new UnsupportedOperationException(); + if (value != null) { + if (value.getFirst() == ResourceType.STYLE) { + // look for the style in the current theme, and its parent: + ResourceValue item = mRenderResources.findItemInTheme(value.getSecond()); + if (item != null) { + if (item instanceof StyleResourceValue) { + if (defaultPropMap != null) { + defaultPropMap.put("style", item.getName()); + } + + defStyleValues = (StyleResourceValue)item; + } + } else { + Bridge.getLog().error(null, + String.format( + "Style with id 0x%x (resolved to '%s') does not exist.", + defStyleRes, value.getSecond()), + null /*data*/); + } + } else { + Bridge.getLog().error(null, + String.format( + "Resouce id 0x%x is not of type STYLE (instead %s)", + defStyleRes, value.getFirst().toString()), + null /*data*/); + } + } else { + Bridge.getLog().error(null, + String.format( + "Failed to find style with id 0x%x in current theme", + defStyleRes), + null /*data*/); + } + } } String namespace = BridgeConstants.NS_RESOURCES; @@ -312,37 +505,43 @@ public final class BridgeContext extends Context { String name = styleAttribute.getValue(); String value = null; - if (parser != null) { - value = parser.getAttributeValue(namespace, name); + if (set != null) { + value = set.getAttributeValue(namespace, name); } // if there's no direct value for this attribute in the XML, we look for default // values in the widget defStyle, and then in the theme. if (value == null) { - IResourceValue resValue = null; + ResourceValue resValue = null; // look for the value in the defStyle first (and its parent if needed) if (defStyleValues != null) { - resValue = findItemInStyle(defStyleValues, name); + resValue = mRenderResources.findItemInStyle(defStyleValues, name); } // if the item is not present in the defStyle, we look in the main theme (and // its parent themes) - if (resValue == null && mThemeValues != null) { - resValue = findItemInStyle(mThemeValues, name); + if (resValue == null) { + resValue = mRenderResources.findItemInTheme(name); } // if we found a value, we make sure this doesn't reference another value. // So we resolve it. if (resValue != null) { - resValue = resolveResValue(resValue); + // put the first default value, before the resolution. + if (defaultPropMap != null) { + defaultPropMap.put(name, resValue.getValue()); + } + + resValue = mRenderResources.resolveResValue(resValue); } ta.bridgeSetValue(index, name, resValue); } else { // there is a value in the XML, but we need to resolve it in case it's // referencing another resource or a theme value. - ta.bridgeSetValue(index, name, resolveValue(null, name, value)); + ta.bridgeSetValue(index, name, + mRenderResources.resolveValue(null, name, value, isPlatformFile)); } } } @@ -365,11 +564,11 @@ public final class BridgeContext extends Context { * values found in the given style. * @see #obtainStyledAttributes(int, int[]) */ - private BridgeTypedArray createStyleBasedTypedArray(IStyleResourceValue style, int[] attrs) + private BridgeTypedArray createStyleBasedTypedArray(StyleResourceValue style, int[] attrs) throws Resources.NotFoundException { TreeMap<Integer, String> styleNameMap = searchAttrs(attrs, null); - BridgeTypedArray ta = ((BridgeResources) mResources).newTypeArray(attrs.length, + BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length, false /* platformResourceFlag */); // loop through all the values in the style map, and init the TypedArray with @@ -380,10 +579,10 @@ public final class BridgeContext extends Context { String name = styleAttribute.getValue(); // get the value from the style, or its parent styles. - IResourceValue resValue = findItemInStyle(style, name); + ResourceValue resValue = mRenderResources.findItemInStyle(style, name); // resolve it to make sure there are no references left. - ta.bridgeSetValue(index, name, resolveResValue(resValue)); + ta.bridgeSetValue(index, name, mRenderResources.resolveResValue(resValue)); } ta.sealArray(); @@ -393,274 +592,6 @@ public final class BridgeContext extends Context { /** - * Resolves the value of a resource, if the value references a theme or resource value. - * <p/> - * This method ensures that it returns a {@link IResourceValue} object that does not - * reference another resource. - * If the resource cannot be resolved, it returns <code>null</code>. - * <p/> - * If a value that does not need to be resolved is given, the method will return a new - * instance of IResourceValue that contains the input value. - * - * @param type the type of the resource - * @param name the name of the attribute containing this value. - * @param value the resource value, or reference to resolve - * @return the resolved resource value or <code>null</code> if it failed to resolve it. - */ - private IResourceValue resolveValue(String type, String name, String value) { - if (value == null) { - return null; - } - - // get the IResourceValue referenced by this value - IResourceValue resValue = findResValue(value, false /*forceFrameworkOnly*/); - - // if resValue is null, but value is not null, this means it was not a reference. - // we return the name/value wrapper in a IResourceValue - if (resValue == null) { - return new ResourceValue(type, name, value); - } - - // we resolved a first reference, but we need to make sure this isn't a reference also. - return resolveResValue(resValue); - } - - /** - * Returns the {@link IResourceValue} referenced by the value of <var>value</var>. - * <p/> - * This method ensures that it returns a {@link IResourceValue} object that does not - * reference another resource. - * If the resource cannot be resolved, it returns <code>null</code>. - * <p/> - * If a value that does not need to be resolved is given, the method will return the input - * value. - * - * @param value the value containing the reference to resolve. - * @return a {@link IResourceValue} object or <code>null</code> - */ - IResourceValue resolveResValue(IResourceValue value) { - if (value == null) { - return null; - } - - // if the resource value is a style, we simply return it. - if (value instanceof IStyleResourceValue) { - return value; - } - - // else attempt to find another IResourceValue referenced by this one. - IResourceValue resolvedValue = findResValue(value.getValue(), value.isFramework()); - - // if the value did not reference anything, then we simply return the input value - if (resolvedValue == null) { - return value; - } - - // otherwise, we attempt to resolve this new value as well - return resolveResValue(resolvedValue); - } - - /** - * Searches for, and returns a {@link IResourceValue} by its reference. - * <p/> - * The reference format can be: - * <pre>@resType/resName</pre> - * <pre>@android:resType/resName</pre> - * <pre>@resType/android:resName</pre> - * <pre>?resType/resName</pre> - * <pre>?android:resType/resName</pre> - * <pre>?resType/android:resName</pre> - * Any other string format will return <code>null</code>. - * <p/> - * The actual format of a reference is <pre>@[namespace:]resType/resName</pre> but this method - * only support the android namespace. - * - * @param reference the resource reference to search for. - * @param forceFrameworkOnly if true all references are considered to be toward framework - * resource even if the reference does not include the android: prefix. - * @return a {@link IResourceValue} or <code>null</code>. - */ - IResourceValue findResValue(String reference, boolean forceFrameworkOnly) { - if (reference == null) { - return null; - } - if (reference.startsWith(BridgeConstants.PREFIX_THEME_REF)) { - // no theme? no need to go further! - if (mThemeValues == null) { - return null; - } - - boolean frameworkOnly = false; - - // eleminate the prefix from the string - if (reference.startsWith(BridgeConstants.PREFIX_ANDROID_THEME_REF)) { - frameworkOnly = true; - reference = reference.substring(BridgeConstants.PREFIX_ANDROID_THEME_REF.length()); - } else { - reference = reference.substring(BridgeConstants.PREFIX_THEME_REF.length()); - } - - // at this point, value can contain type/name (drawable/foo for instance). - // split it to make sure. - String[] segments = reference.split("\\/"); - - // we look for the referenced item name. - String referenceName = null; - - if (segments.length == 2) { - // there was a resType in the reference. If it's attr, we ignore it - // else, we assert for now. - if (BridgeConstants.RES_ATTR.equals(segments[0])) { - referenceName = segments[1]; - } else { - // At this time, no support for ?type/name where type is not "attr" - return null; - } - } else { - // it's just an item name. - referenceName = segments[0]; - } - - // now we look for android: in the referenceName in order to support format - // such as: ?attr/android:name - if (referenceName.startsWith(BridgeConstants.PREFIX_ANDROID)) { - frameworkOnly = true; - referenceName = referenceName.substring(BridgeConstants.PREFIX_ANDROID.length()); - } - - // Now look for the item in the theme, starting with the current one. - if (frameworkOnly) { - // FIXME for now we do the same as if it didn't specify android: - return findItemInStyle(mThemeValues, referenceName); - } - - return findItemInStyle(mThemeValues, referenceName); - } else if (reference.startsWith(BridgeConstants.PREFIX_RESOURCE_REF)) { - boolean frameworkOnly = false; - - // check for the specific null reference value. - if (BridgeConstants.REFERENCE_NULL.equals(reference)) { - return null; - } - - // Eliminate the prefix from the string. - if (reference.startsWith(BridgeConstants.PREFIX_ANDROID_RESOURCE_REF)) { - frameworkOnly = true; - reference = reference.substring( - BridgeConstants.PREFIX_ANDROID_RESOURCE_REF.length()); - } else { - reference = reference.substring(BridgeConstants.PREFIX_RESOURCE_REF.length()); - } - - // at this point, value contains type/[android:]name (drawable/foo for instance) - String[] segments = reference.split("\\/"); - - // now we look for android: in the resource name in order to support format - // such as: @drawable/android:name - if (segments[1].startsWith(BridgeConstants.PREFIX_ANDROID)) { - frameworkOnly = true; - segments[1] = segments[1].substring(BridgeConstants.PREFIX_ANDROID.length()); - } - - return findResValue(segments[0], segments[1], - forceFrameworkOnly ? true :frameworkOnly); - } - - // Looks like the value didn't reference anything. Return null. - return null; - } - - /** - * Searches for, and returns a {@link IResourceValue} by its name, and type. - * @param resType the type of the resource - * @param resName the name of the resource - * @param frameworkOnly if <code>true</code>, the method does not search in the - * project resources - */ - private IResourceValue findResValue(String resType, String resName, boolean frameworkOnly) { - // map of IResouceValue for the given type - Map<String, IResourceValue> typeMap; - - // if allowed, search in the project resources first. - if (frameworkOnly == false) { - typeMap = mProjectResources.get(resType); - if (typeMap != null) { - IResourceValue item = typeMap.get(resName); - if (item != null) { - return item; - } - } - } - - // now search in the framework resources. - typeMap = mFrameworkResources.get(resType); - if (typeMap != null) { - IResourceValue item = typeMap.get(resName); - if (item != null) { - return item; - } - } - - // didn't find the resource anywhere. - return null; - } - - /** - * Returns a framework resource by type and name. The returned resource is resolved. - * @param resourceType the type of the resource - * @param resourceName the name of the resource - */ - public IResourceValue getFrameworkResource(String resourceType, String resourceName) { - return getResource(resourceType, resourceName, mFrameworkResources); - } - - /** - * Returns a project resource by type and name. The returned resource is resolved. - * @param resourceType the type of the resource - * @param resourceName the name of the resource - */ - public IResourceValue getProjectResource(String resourceType, String resourceName) { - return getResource(resourceType, resourceName, mProjectResources); - } - - IResourceValue getResource(String resourceType, String resourceName, - Map<String, Map<String, IResourceValue>> resourceRepository) { - Map<String, IResourceValue> typeMap = resourceRepository.get(resourceType); - if (typeMap != null) { - IResourceValue item = typeMap.get(resourceName); - if (item != null) { - item = resolveResValue(item); - return item; - } - } - - // didn't find the resource anywhere. - return null; - - } - - /** - * Returns the {@link IResourceValue} matching a given name in a given style. If the - * item is not directly available in the style, the method looks in its parent style. - * @param style the style to search in - * @param itemName the name of the item to search for. - * @return the {@link IResourceValue} object or <code>null</code> - */ - IResourceValue findItemInStyle(IStyleResourceValue style, String itemName) { - IResourceValue item = style.findItem(itemName); - - // if we didn't find it, we look in the parent style (if applicable) - if (item == null && mStyleInheritanceMap != null) { - IStyleResourceValue parentStyle = mStyleInheritanceMap.get(style); - if (parentStyle != null) { - return findItemInStyle(parentStyle, itemName); - } - } - - return item; - } - - /** * The input int[] attrs is one of com.android.internal.R.styleable fields where the name * of the field is the style being referenced and the array contains one index per attribute. * <p/> @@ -675,14 +606,14 @@ public final class BridgeContext extends Context { */ private TreeMap<Integer,String> searchAttrs(int[] attrs, boolean[] outFrameworkFlag) { // get the name of the array from the framework resources - String arrayName = Bridge.resolveResourceValue(attrs); + String arrayName = Bridge.resolveResourceId(attrs); if (arrayName != null) { // if we found it, get the name of each of the int in the array. TreeMap<Integer,String> attributes = new TreeMap<Integer, String>(); for (int i = 0 ; i < attrs.length ; i++) { - String[] info = Bridge.resolveResourceValue(attrs[i]); + Pair<ResourceType, String> info = Bridge.resolveResourceId(attrs[i]); if (info != null) { - attributes.put(i, info[0]); + attributes.put(i, info.getSecond()); } else { // FIXME Not sure what we should be doing here... attributes.put(i, null); @@ -698,13 +629,13 @@ public final class BridgeContext extends Context { // if the name was not found in the framework resources, look in the project // resources - arrayName = mProjectCallback.resolveResourceValue(attrs); + arrayName = mProjectCallback.resolveResourceId(attrs); if (arrayName != null) { TreeMap<Integer,String> attributes = new TreeMap<Integer, String>(); for (int i = 0 ; i < attrs.length ; i++) { - String[] info = mProjectCallback.resolveResourceValue(attrs[i]); + Pair<ResourceType, String> info = mProjectCallback.resolveResourceId(attrs[i]); if (info != null) { - attributes.put(i, info[0]); + attributes.put(i, info.getSecond()); } else { // FIXME Not sure what we should be doing here... attributes.put(i, null); @@ -729,24 +660,24 @@ public final class BridgeContext extends Context { * if nothing is found. */ public String searchAttr(int attr) { - String[] info = Bridge.resolveResourceValue(attr); + Pair<ResourceType, String> info = Bridge.resolveResourceId(attr); if (info != null) { - return info[0]; + return info.getSecond(); } - info = mProjectCallback.resolveResourceValue(attr); + info = mProjectCallback.resolveResourceId(attr); if (info != null) { - return info[0]; + return info.getSecond(); } return null; } - int getDynamicIdByStyle(IStyleResourceValue resValue) { + int getDynamicIdByStyle(StyleResourceValue resValue) { if (mDynamicIdToStyleMap == null) { // create the maps. - mDynamicIdToStyleMap = new HashMap<Integer, IStyleResourceValue>(); - mStyleToDynamicIdMap = new HashMap<IStyleResourceValue, Integer>(); + mDynamicIdToStyleMap = new HashMap<Integer, StyleResourceValue>(); + mStyleToDynamicIdMap = new HashMap<StyleResourceValue, Integer>(); } // look for an existing id @@ -764,7 +695,7 @@ public final class BridgeContext extends Context { return id; } - private IStyleResourceValue getStyleByDynamicId(int i) { + private StyleResourceValue getStyleByDynamicId(int i) { if (mDynamicIdToStyleMap != null) { return mDynamicIdToStyleMap.get(i); } @@ -772,8 +703,8 @@ public final class BridgeContext extends Context { return null; } - int getFrameworkIdValue(String idName, int defValue) { - Integer value = Bridge.getResourceValue(BridgeConstants.RES_ID, idName); + int getFrameworkResourceValue(ResourceType resType, String resName, int defValue) { + Integer value = Bridge.getResourceId(resType, resName); if (value != null) { return value.intValue(); } @@ -781,9 +712,9 @@ public final class BridgeContext extends Context { return defValue; } - int getProjectIdValue(String idName, int defValue) { + int getProjectResourceValue(ResourceType resType, String resName, int defValue) { if (mProjectCallback != null) { - Integer value = mProjectCallback.getResourceValue(BridgeConstants.RES_ID, idName); + Integer value = mProjectCallback.getResourceId(resType, resName); if (value != null) { return value.intValue(); } @@ -1000,7 +931,7 @@ public final class BridgeContext extends Context { @Override public ApplicationInfo getApplicationInfo() { - return new ApplicationInfo(); + return mApplicationInfo; } @Override @@ -1043,25 +974,27 @@ public final class BridgeContext extends Context { } - @SuppressWarnings("unused") @Override - public FileInputStream openFileInput(String arg0) - throws FileNotFoundException { + public FileInputStream openFileInput(String arg0) throws FileNotFoundException { // TODO Auto-generated method stub return null; } - @SuppressWarnings("unused") @Override - public FileOutputStream openFileOutput(String arg0, int arg1) - throws FileNotFoundException { + public FileOutputStream openFileOutput(String arg0, int arg1) throws FileNotFoundException { + // TODO Auto-generated method stub + return null; + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1, CursorFactory arg2) { // TODO Auto-generated method stub return null; } @Override public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1, - CursorFactory arg2) { + CursorFactory arg2, DatabaseErrorHandler arg3) { // TODO Auto-generated method stub return null; } @@ -1142,14 +1075,12 @@ public final class BridgeContext extends Context { } - @SuppressWarnings("unused") @Override public void setWallpaper(Bitmap arg0) throws IOException { // TODO Auto-generated method stub } - @SuppressWarnings("unused") @Override public void setWallpaper(InputStream arg0) throws IOException { // TODO Auto-generated method stub @@ -1204,4 +1135,15 @@ public final class BridgeContext extends Context { public Context getApplicationContext() { throw new UnsupportedOperationException(); } + + @Override + public void startActivities(Intent[] arg0) { + // TODO Auto-generated method stub + + } + + @Override + public boolean isRestricted() { + return false; + } } diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java index 0910d79..5740e8b 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java @@ -14,36 +14,42 @@ * limitations under the License. */ -package android.view; +package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.IResourceValue; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.MergeCookie; +import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; -import com.android.layoutlib.bridge.BridgeConstants; -import com.android.layoutlib.bridge.BridgeContext; -import com.android.layoutlib.bridge.BridgeXmlBlockParser; +import com.android.resources.ResourceType; +import com.android.util.Pair; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; import android.content.Context; import android.util.AttributeSet; +import android.view.InflateException; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import java.io.File; import java.io.FileReader; /** - * Custom implementation of {@link LayoutInflater} to handle custom views. + * Custom implementation of {@link LayoutInflater} to handle custom views. */ public final class BridgeInflater extends LayoutInflater { - + private final IProjectCallback mProjectCallback; + private boolean mIsInMerge = false; /** * List of class prefixes which are tried first by default. * <p/> * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater. - */ + */ private static final String[] sClassPrefixList = { "android.widget.", "android.webkit." @@ -53,10 +59,10 @@ public final class BridgeInflater extends LayoutInflater { super(original, newContext); mProjectCallback = null; } - + /** * Instantiate a new BridgeInflater with an {@link IProjectCallback} object. - * + * * @param context The Android application context. * @param projectCallback the {@link IProjectCallback} object. */ @@ -82,7 +88,7 @@ public final class BridgeInflater extends LayoutInflater { // Ignore. We'll try again using the base class below. } } - + // Next try using the parent loader. This will most likely only work for // fully-qualified class names. try { @@ -92,7 +98,7 @@ public final class BridgeInflater extends LayoutInflater { } catch (ClassNotFoundException e) { // Ignore. We'll try again using the custom view loader below. } - + // Finally try again using the custom view loader try { if (view == null) { @@ -109,17 +115,17 @@ public final class BridgeInflater extends LayoutInflater { ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e); throw exception; } - + setupViewInContext(view, attrs); - + return view; } - + @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 { @@ -128,7 +134,7 @@ public final class BridgeInflater extends LayoutInflater { // Wrap the real exception in an InflateException so that the calling // method can deal with it. InflateException exception = new InflateException(); - if (e2.getClass().equals(ClassNotFoundException.class) == false) { + if (e2.getClass().equals(ClassNotFoundException.class) == false) { exception.initCause(e2); } else { exception.initCause(e); @@ -136,30 +142,30 @@ public final class BridgeInflater extends LayoutInflater { throw exception; } } - + setupViewInContext(view, attrs); - + return view; } - + @Override public View inflate(int resource, ViewGroup root) { Context context = getContext(); if (context instanceof BridgeContext) { BridgeContext bridgeContext = (BridgeContext)context; - - IResourceValue value = null; - String[] layoutInfo = Bridge.resolveResourceValue(resource); + ResourceValue value = null; + + Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource); if (layoutInfo != null) { - value = bridgeContext.getFrameworkResource(BridgeConstants.RES_LAYOUT, - layoutInfo[0]); + value = bridgeContext.getRenderResources().getFrameworkResource( + ResourceType.LAYOUT, layoutInfo.getSecond()); } else { - layoutInfo = mProjectCallback.resolveResourceValue(resource); - + layoutInfo = mProjectCallback.resolveResourceId(resource); + if (layoutInfo != null) { - value = bridgeContext.getProjectResource(BridgeConstants.RES_LAYOUT, - layoutInfo[0]); + value = bridgeContext.getRenderResources().getProjectResource( + ResourceType.LAYOUT, layoutInfo.getSecond()); } } @@ -170,21 +176,23 @@ public final class BridgeInflater extends LayoutInflater { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new FileReader(f)); - + BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( parser, bridgeContext, false); - + return inflate(bridgeParser, root); } catch (Exception e) { - bridgeContext.getLogger().error(e); - // return null below. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed to parse file " + f.getAbsolutePath(), e, null /*data*/); + + return null; } } } } return null; } - + private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException, Exception{ if (mProjectCallback != null) { @@ -192,12 +200,12 @@ public final class BridgeInflater extends LayoutInflater { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } - + mConstructorArgs[1] = attrs; Object customView = mProjectCallback.loadView(name, mConstructorSignature, mConstructorArgs); - + if (customView instanceof View) { return (View)customView; } @@ -205,21 +213,43 @@ public final class BridgeInflater extends LayoutInflater { return null; } - - - + private void setupViewInContext(View view, AttributeSet attrs) { if (getContext() instanceof BridgeContext) { BridgeContext bc = (BridgeContext) getContext(); if (attrs instanceof BridgeXmlBlockParser) { - Object viewKey = ((BridgeXmlBlockParser) attrs).getViewKey(); + BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs; + + // get the view key + Object viewKey = parser.getViewCookie(); + + // if there's no view key and the depth is 1 (ie this is the first tag), or 2 + // (this is first item in included merge layout) + // look for a previous parser in the context, and check if this one has a viewkey. + int testDepth = mIsInMerge ? 2 : 1; + if (viewKey == null && parser.getDepth() == testDepth) { + BridgeXmlBlockParser previousParser = bc.getPreviousParser(); + if (previousParser != null) { + viewKey = previousParser.getViewCookie(); + } + } + if (viewKey != null) { + if (testDepth == 2) { + // include-merge case + viewKey = new MergeCookie(viewKey); + } + bc.addViewKey(view, viewKey); } } } } + public void setIsInMerge(boolean isInMerge) { + mIsInMerge = isInMerge; + } + @Override public LayoutInflater cloneInContext(Context newContext) { return new BridgeInflater(this, newContext); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java new file mode 100644 index 0000000..d208408 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java @@ -0,0 +1,142 @@ +/* + * 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.android; + +import com.android.layoutlib.bridge.BridgeConstants; + +import android.util.AttributeSet; + +import java.util.Map; + +/** + * An implementation of the {@link AttributeSet} interface on top of a map of attribute in the form + * of (name, value). + * + * This is meant to be called only from {@link BridgeContext#obtainStyledAttributes(AttributeSet, int[], int, int)} + * in the case of LayoutParams and therefore isn't a full implementation. + */ +public class BridgeLayoutParamsMapAttributes implements AttributeSet { + + private final Map<String, String> mAttributes; + + public BridgeLayoutParamsMapAttributes(Map<String, String> attributes) { + mAttributes = attributes; + } + + public String getAttributeValue(String namespace, String name) { + if (BridgeConstants.NS_RESOURCES.equals(namespace)) { + return mAttributes.get(name); + } + + return null; + } + + // ---- the following methods are not called from + // BridgeContext#obtainStyledAttributes(AttributeSet, int[], int, int) + // Should they ever be called, we'll just implement them on a need basis. + + public int getAttributeCount() { + throw new UnsupportedOperationException(); + } + + public String getAttributeName(int index) { + throw new UnsupportedOperationException(); + } + + public String getAttributeValue(int index) { + throw new UnsupportedOperationException(); + } + + public String getPositionDescription() { + throw new UnsupportedOperationException(); + } + + public int getAttributeNameResource(int index) { + throw new UnsupportedOperationException(); + } + + public int getAttributeListValue(String namespace, String attribute, + String[] options, int defaultValue) { + throw new UnsupportedOperationException(); + } + + public boolean getAttributeBooleanValue(String namespace, String attribute, + boolean defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeResourceValue(String namespace, String attribute, + int defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeIntValue(String namespace, String attribute, + int defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeUnsignedIntValue(String namespace, String attribute, + int defaultValue) { + throw new UnsupportedOperationException(); + } + + public float getAttributeFloatValue(String namespace, String attribute, + float defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeListValue(int index, + String[] options, int defaultValue) { + throw new UnsupportedOperationException(); + } + + public boolean getAttributeBooleanValue(int index, boolean defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeResourceValue(int index, int defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeIntValue(int index, int defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeUnsignedIntValue(int index, int defaultValue) { + throw new UnsupportedOperationException(); + } + + public float getAttributeFloatValue(int index, float defaultValue) { + throw new UnsupportedOperationException(); + } + + public String getIdAttribute() { + throw new UnsupportedOperationException(); + } + + public String getClassAttribute() { + throw new UnsupportedOperationException(); + } + + public int getIdAttributeResourceValue(int defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getStyleAttribute() { + throw new UnsupportedOperationException(); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java index 6358abb..5e5aeb1 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java @@ -14,10 +14,17 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; - -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.IResourceValue; +package com.android.layoutlib.bridge.android; + +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.ninepatch.NinePatch; +import com.android.resources.ResourceType; +import com.android.util.Pair; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; @@ -52,6 +59,35 @@ public final class BridgeResources extends Resources { private boolean[] mPlatformResourceFlag = new boolean[1]; /** + * Simpler wrapper around FileInputStream. This is used when the input stream represent + * not a normal bitmap but a nine patch. + * This is useful when the InputStream is created in a method but used in another that needs + * to know whether this is 9-patch or not, such as BitmapFactory. + */ + public class NinePatchInputStream extends FileInputStream { + private boolean mFakeMarkSupport = true; + public NinePatchInputStream(File file) throws FileNotFoundException { + super(file); + } + + @Override + public boolean markSupported() { + if (mFakeMarkSupport) { + // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream. + return true; + } + + return super.markSupported(); + } + + public void disableFakeMarkSupport() { + // disable fake mark support so that in case codec actually try to use them + // we don't lie to them. + mFakeMarkSupport = false; + } + } + + /** * This initializes the static field {@link Resources#mSystem} which is used * by methods who get global resources using {@link Resources#getSystem()}. * <p/> @@ -64,21 +100,18 @@ public final class BridgeResources extends Resources { DisplayMetrics metrics, Configuration config, IProjectCallback projectCallback) { - if (!(Resources.mSystem instanceof BridgeResources)) { - Resources.mSystem = new BridgeResources(context, - assets, - metrics, - config, - projectCallback); - } - return Resources.mSystem; + return Resources.mSystem = new BridgeResources(context, + assets, + metrics, + config, + projectCallback); } /** - * Clears the static {@link Resources#mSystem} to make sure we don't leave objects + * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects * around that would prevent us from unloading the library. */ - /*package*/ static void clearSystem() { + /*package*/ static void disposeSystem() { if (Resources.mSystem instanceof BridgeResources) { ((BridgeResources)(Resources.mSystem)).mContext = null; ((BridgeResources)(Resources.mSystem)).mProjectCallback = null; @@ -97,22 +130,24 @@ public final class BridgeResources extends Resources { return new BridgeTypedArray(this, mContext, numEntries, platformFile); } - private IResourceValue getResourceValue(int id, boolean[] platformResFlag_out) { + private ResourceValue getResourceValue(int id, boolean[] platformResFlag_out) { // first get the String related to this id in the framework - String[] resourceInfo = Bridge.resolveResourceValue(id); + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); if (resourceInfo != null) { platformResFlag_out[0] = true; - return mContext.getFrameworkResource(resourceInfo[1], resourceInfo[0]); + return mContext.getRenderResources().getFrameworkResource( + resourceInfo.getFirst(), resourceInfo.getSecond()); } // didn't find a match in the framework? look in the project. if (mProjectCallback != null) { - resourceInfo = mProjectCallback.resolveResourceValue(id); + resourceInfo = mProjectCallback.resolveResourceId(id); if (resourceInfo != null) { platformResFlag_out[0] = false; - return mContext.getProjectResource(resourceInfo[1], resourceInfo[0]); + return mContext.getRenderResources().getProjectResource( + resourceInfo.getFirst(), resourceInfo.getSecond()); } } @@ -121,10 +156,10 @@ public final class BridgeResources extends Resources { @Override public Drawable getDrawable(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { - return ResourceHelper.getDrawable(value, mContext, value.isFramework()); + return ResourceHelper.getDrawable(value, mContext); } // id was not found or not resolved. Throw a NotFoundException. @@ -136,12 +171,14 @@ public final class BridgeResources extends Resources { @Override public int getColor(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { try { return ResourceHelper.getColor(value.getValue()); } catch (NumberFormatException e) { + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, + null /*data*/); return 0; } } @@ -155,14 +192,12 @@ public final class BridgeResources extends Resources { @Override public ColorStateList getColorStateList(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue resValue = getResourceValue(id, mPlatformResourceFlag); - if (value != null) { - try { - int color = ResourceHelper.getColor(value.getValue()); - return ColorStateList.valueOf(color); - } catch (NumberFormatException e) { - return null; + if (resValue != null) { + ColorStateList stateList = ResourceHelper.getColorStateList(resValue, mContext); + if (stateList != null) { + return stateList; } } @@ -175,7 +210,7 @@ public final class BridgeResources extends Resources { @Override public CharSequence getText(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { return value.getValue(); @@ -190,27 +225,76 @@ public final class BridgeResources extends Resources { @Override public XmlResourceParser getLayout(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { - File xml = new File(value.getValue()); - if (xml.isFile()) { - // we need to create a pull parser around the layout XML file, and then - // give that to our XmlBlockParser - try { - KXmlParser parser = new KXmlParser(); + XmlPullParser parser = null; + + try { + // check if the current parser can provide us with a custom parser. + BridgeXmlBlockParser currentParser = mContext.getCurrentParser(); + if (currentParser != null) { + parser = currentParser.getParser(value.getName()); + } + + // create a new one manually if needed. + if (parser == null) { + File xml = new File(value.getValue()); + if (xml.isFile()) { + // we need to create a pull parser around the layout XML file, and then + // give that to our XmlBlockParser + parser = new KXmlParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new FileReader(xml)); + } + } + + if (parser != null) { + return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value.getValue(), e, null /*data*/); + // we'll return null below. + } catch (FileNotFoundException e) { + // this shouldn't happen since we check above. + } + + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public XmlResourceParser getAnimation(int id) throws NotFoundException { + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + XmlPullParser parser = null; + + try { + File xml = new File(value.getValue()); + if (xml.isFile()) { + // we need to create a pull parser around the layout XML file, and then + // give that to our XmlBlockParser + parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new FileReader(xml)); return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); - } catch (XmlPullParserException e) { - mContext.getLogger().error(e); - - // we'll return null below. - } catch (FileNotFoundException e) { - // this shouldn't happen since we check above. } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value.getValue(), e, null /*data*/); + // we'll return null below. + } catch (FileNotFoundException e) { + // this shouldn't happen since we check above. } + } // id was not found or not resolved. Throw a NotFoundException. @@ -233,7 +317,7 @@ public final class BridgeResources extends Resources { @Override public float getDimension(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { String v = value.getValue(); @@ -262,7 +346,7 @@ public final class BridgeResources extends Resources { @Override public int getDimensionPixelOffset(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { String v = value.getValue(); @@ -284,7 +368,7 @@ public final class BridgeResources extends Resources { @Override public int getDimensionPixelSize(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { String v = value.getValue(); @@ -306,7 +390,7 @@ public final class BridgeResources extends Resources { @Override public int getInteger(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null && value.getValue() != null) { String v = value.getValue(); @@ -361,7 +445,7 @@ public final class BridgeResources extends Resources { @Override public String getString(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null && value.getValue() != null) { return value.getValue(); @@ -377,7 +461,7 @@ public final class BridgeResources extends Resources { @Override public void getValue(int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { String v = value.getValue(); @@ -406,7 +490,7 @@ public final class BridgeResources extends Resources { @Override public XmlResourceParser getXml(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { String v = value.getValue(); @@ -470,16 +554,22 @@ public final class BridgeResources extends Resources { @Override public InputStream openRawResource(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { - String v = value.getValue(); + String path = value.getValue(); - if (v != null) { + if (path != null) { // check this is a file - File f = new File(value.getValue()); + File f = new File(path); if (f.isFile()) { try { + // if it's a nine-patch return a custom input stream so that + // other methods (mainly bitmap factory) can detect it's a 9-patch + // and actually load it as a 9-patch instead of a normal bitmap + if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { + return new NinePatchInputStream(f); + } return new FileInputStream(f); } catch (FileNotFoundException e) { NotFoundException newE = new NotFoundException(); @@ -501,9 +591,17 @@ public final class BridgeResources extends Resources { public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { getValue(id, value, true); - File f = new File(value.string.toString()); + String path = value.string.toString(); + + File f = new File(path); if (f.isFile()) { try { + // if it's a nine-patch return a custom input stream so that + // other methods (mainly bitmap factory) can detect it's a 9-patch + // and actually load it as a 9-patch instead of a normal bitmap + if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { + return new NinePatchInputStream(f); + } return new FileInputStream(f); } catch (FileNotFoundException e) { NotFoundException exception = new NotFoundException(); @@ -527,18 +625,18 @@ public final class BridgeResources extends Resources { */ private void throwException(int id) throws NotFoundException { // first get the String related to this id in the framework - String[] resourceInfo = Bridge.resolveResourceValue(id); + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); // if the name is unknown in the framework, get it from the custom view loader. if (resourceInfo == null && mProjectCallback != null) { - resourceInfo = mProjectCallback.resolveResourceValue(id); + resourceInfo = mProjectCallback.resolveResourceId(id); } String message = null; if (resourceInfo != null) { message = String.format( "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", - resourceInfo[1], id, resourceInfo[0]); + resourceInfo.getFirst(), id, resourceInfo.getSecond()); } else { message = String.format( "Could not resolve resource value: 0x%1$X.", id); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java index 70c5bd7..c1d7600 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java @@ -14,14 +14,21 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.android; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.internal.util.XmlUtils; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IStyleResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.resources.ResourceType; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -33,39 +40,38 @@ import android.view.ViewGroup.LayoutParams; import java.io.File; import java.io.FileReader; +import java.util.Arrays; import java.util.Map; /** - * TODO: describe. + * Custom implementation of TypedArray to handle non compiled resources. */ public final class BridgeTypedArray extends TypedArray { - @SuppressWarnings("hiding") - private BridgeResources mResources; + private BridgeResources mBridgeResources; private BridgeContext mContext; - @SuppressWarnings("hiding") - private IResourceValue[] mData; + private ResourceValue[] mResourceData; private String[] mNames; private final boolean mPlatformFile; public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, boolean platformFile) { super(null, null, null, 0); - mResources = resources; + mBridgeResources = resources; mContext = context; mPlatformFile = platformFile; - mData = new IResourceValue[len]; + mResourceData = new ResourceValue[len]; mNames = new String[len]; } /** A bridge-specific method that sets a value in the type array */ - public void bridgeSetValue(int index, String name, IResourceValue value) { - mData[index] = value; + public void bridgeSetValue(int index, String name, ResourceValue value) { + mResourceData[index] = value; mNames[index] = name; } /** - * Seals the array after all calls to {@link #bridgeSetValue(int, String, IResourceValue)} have + * Seals the array after all calls to {@link #bridgeSetValue(int, String, ResourceValue)} have * been done. * <p/>This allows to compute the list of non default values, permitting * {@link #getIndexCount()} to return the proper value. @@ -74,7 +80,7 @@ public final class BridgeTypedArray extends TypedArray { // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt // first count the array size int count = 0; - for (IResourceValue data : mData) { + for (ResourceValue data : mResourceData) { if (data != null) { count++; } @@ -86,8 +92,8 @@ public final class BridgeTypedArray extends TypedArray { // fill the array with the indices. int index = 1; - for (int i = 0 ; i < mData.length ; i++) { - if (mData[i] != null) { + for (int i = 0 ; i < mResourceData.length ; i++) { + if (mResourceData[i] != null) { mIndices[index++] = i; } } @@ -98,7 +104,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int length() { - return mData.length; + return mResourceData.length; } /** @@ -106,7 +112,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public Resources getResources() { - return mResources; + return mBridgeResources; } /** @@ -119,9 +125,9 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence getText(int index) { - if (mData[index] != null) { + if (mResourceData[index] != null) { // FIXME: handle styled strings! - return mData[index].getValue(); + return mResourceData[index].getValue(); } return null; @@ -137,8 +143,8 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public String getString(int index) { - if (mData[index] != null) { - return mData[index].getValue(); + if (mResourceData[index] != null) { + return mResourceData[index].getValue(); } return null; @@ -154,11 +160,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean getBoolean(int index, boolean defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s != null) { return XmlUtils.convertValueToBoolean(s, defValue); } @@ -176,11 +182,15 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getInt(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); + + if (RenderResources.REFERENCE_NULL.equals(s)) { + return defValue; + } try { return (s == null) ? defValue : XmlUtils.convertValueToInt(s, defValue); @@ -204,9 +214,10 @@ public final class BridgeTypedArray extends TypedArray { if (i != null) { result |= i.intValue(); } else { - mContext.getLogger().warning(String.format( - "Unknown constant \"%s\" in attribute \"%2$s\"", - keyword, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%s\" in attribute \"%2$s\" is not a valid value", + keyword, mNames[index]), null /*data*/); } } return result; @@ -224,19 +235,20 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getFloat(int index, float defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s != null) { try { return Float.parseFloat(s); } catch (NumberFormatException e) { - mContext.getLogger().warning(String.format( - "Unable to convert \"%s\" into a float in attribute \"%2$s\"", - s, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%s\" in attribute \"%2$s\" cannot be converted to float.", + s, mNames[index]), null /*data*/); // we'll return the default value below. } @@ -258,19 +270,14 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getColor(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); - try { - return ResourceHelper.getColor(s); - } catch (NumberFormatException e) { - mContext.getLogger().warning(String.format( - "Unable to convert \"%s\" into a color in attribute \"%2$s\"", - s, mNames[index])); - - // we'll return the default value below. + ColorStateList colorStateList = ResourceHelper.getColorStateList( + mResourceData[index], mContext); + if (colorStateList != null) { + return colorStateList.getDefaultColor(); } return defValue; @@ -287,49 +294,56 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public ColorStateList getColorStateList(int index) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return null; } - String value = mData[index].getValue(); + ResourceValue resValue = mResourceData[index]; + String value = resValue.getValue(); if (value == null) { return null; } - try { - int color = ResourceHelper.getColor(value); - return ColorStateList.valueOf(color); - } catch (NumberFormatException e) { - // if it's not a color value, we'll attempt to read the xml based color below. + if (RenderResources.REFERENCE_NULL.equals(value)) { + return null; } // let the framework inflate the ColorStateList from the XML file. - try { - File f = new File(value); - if (f.isFile()) { + File f = new File(value); + if (f.isFile()) { + try { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new FileReader(f)); - ColorStateList colorStateList = ColorStateList.createFromXml( - mContext.getResources(), - // FIXME: we need to know if this resource is platform or not - new BridgeXmlBlockParser(parser, mContext, false)); - return colorStateList; + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( + parser, mContext, resValue.isFramework()); + try { + return ColorStateList.createFromXml(mContext.getResources(), blockParser); + } finally { + blockParser.ensurePopped(); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value, e, null /*data*/); + return null; + } catch (Exception e) { + // this is an error and not warning since the file existence is checked before + // attempting to parse it. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed to parse file " + value, e, null /*data*/); + + return null; } - } catch (Exception e) { - // this is an error and not warning since the file existence is checked before - // attempting to parse it. - mContext.getLogger().error(e); - - // return null below. } - // looks like were unable to resolve the color value. - mContext.getLogger().warning(String.format( - "Unable to resolve color value \"%1$s\" in attribute \"%2$s\"", - value, mNames[index])); + try { + int color = ResourceHelper.getColor(value); + return ColorStateList.valueOf(color); + } catch (NumberFormatException e) { + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null /*data*/); + } return null; } @@ -345,19 +359,20 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getInteger(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s != null) { try { return Integer.parseInt(s); } catch (NumberFormatException e) { - mContext.getLogger().warning(String.format( - "Unable to convert \"%s\" into a integer in attribute \"%2$s\"", - s, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%s\" in attribute \"%2$s\" cannont be converted to an integer.", + s, mNames[index]), null /*data*/); // The default value is returned below. } @@ -384,11 +399,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getDimension(int index, float defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s == null) { return defValue; @@ -399,14 +414,19 @@ public final class BridgeTypedArray extends TypedArray { return LayoutParams.WRAP_CONTENT; } + if (RenderResources.REFERENCE_NULL.equals(s)) { + return defValue; + } + if (ResourceHelper.stringToFloat(s, mValue)) { - return mValue.getDimension(mResources.mMetrics); + return mValue.getDimension(mBridgeResources.mMetrics); } // looks like we were unable to resolve the dimension value - mContext.getLogger().warning(String.format( - "Unable to resolve dimension value \"%1$s\" in attribute \"%2$s\"", - s, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%1$s\" in attribute \"%2$s\" is not a valid format.", + s, mNames[index]), null /*data*/); return defValue; } @@ -453,11 +473,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getDimensionPixelSize(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s == null) { return defValue; @@ -468,6 +488,10 @@ public final class BridgeTypedArray extends TypedArray { return LayoutParams.WRAP_CONTENT; } + if (RenderResources.REFERENCE_NULL.equals(s)) { + return defValue; + } + // FIXME huh? float f = getDimension(index, defValue); @@ -476,8 +500,11 @@ public final class BridgeTypedArray extends TypedArray { if (f == 0) return 0; if (f > 0) return 1; - throw new UnsupportedOperationException("Can't convert to dimension: " + - Integer.toString(index)); + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, + "Can't convert to dimension: " + Integer.toString(index), + null, null /*data*/); + + return defValue; } /** @@ -519,11 +546,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getFraction(int index, int base, int pbase, float defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String value = mData[index].getValue(); + String value = mResourceData[index].getValue(); if (value == null) { return defValue; } @@ -533,9 +560,10 @@ public final class BridgeTypedArray extends TypedArray { } // looks like we were unable to resolve the fraction value - mContext.getLogger().warning(String.format( - "Unable to resolve fraction value \"%1$s\" in attribute \"%2$s\"", - value, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%1$s\" in attribute \"%2$s\" cannont be converted to a fraction.", + value, mNames[index]), null /*data*/); return defValue; } @@ -556,8 +584,8 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getResourceId(int index, int defValue) { - // get the IResource for this index - IResourceValue resValue = mData[index]; + // get the Resource for this index + ResourceValue resValue = mResourceData[index]; // no data, return the default value. if (resValue == null) { @@ -565,24 +593,30 @@ public final class BridgeTypedArray extends TypedArray { } // check if this is a style resource - if (resValue instanceof IStyleResourceValue) { + if (resValue instanceof StyleResourceValue) { // get the id that will represent this style. - return mContext.getDynamicIdByStyle((IStyleResourceValue)resValue); + return mContext.getDynamicIdByStyle((StyleResourceValue)resValue); + } + + if (RenderResources.REFERENCE_NULL.equals(resValue.getValue())) { + return defValue; } - // if the attribute was a reference to an id, and not a declaration of an id (@+id), then - // the xml attribute value was "resolved" which leads us to a IResourceValue with - // getType() returning "id" and getName() returning the id name + // if the attribute was a reference to a resource, and not a declaration of an id (@+id), + // then the xml attribute value was "resolved" which leads us to a ResourceValue with a + // valid getType() and getName() returning a resource name. // (and getValue() returning null!). We need to handle this! - if (resValue.getType() != null && resValue.getType().equals(BridgeConstants.RES_ID)) { + if (resValue.getResourceType() != null) { // if this is a framework id if (mPlatformFile || resValue.isFramework()) { // look for idName in the android R classes - return mContext.getFrameworkIdValue(resValue.getName(), defValue); + return mContext.getFrameworkResourceValue( + resValue.getResourceType(), resValue.getName(), defValue); } // look for idName in the project R class. - return mContext.getProjectIdValue(resValue.getName(), defValue); + return mContext.getProjectResourceValue( + resValue.getResourceType(), resValue.getName(), defValue); } // else, try to get the value, and resolve it somehow. @@ -619,29 +653,33 @@ public final class BridgeTypedArray extends TypedArray { // if this is a framework id if (mPlatformFile || value.startsWith("@android") || value.startsWith("@+android")) { // look for idName in the android R classes - return mContext.getFrameworkIdValue(idName, defValue); + return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue); } // look for idName in the project R class. - return mContext.getProjectIdValue(idName, defValue); + return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue); } // not a direct id valid reference? resolve it Integer idValue = null; if (resValue.isFramework()) { - idValue = Bridge.getResourceValue(resValue.getType(), resValue.getName()); + idValue = Bridge.getResourceId(resValue.getResourceType(), + resValue.getName()); } else { - idValue = mContext.getProjectCallback().getResourceValue( - resValue.getType(), resValue.getName()); + idValue = mContext.getProjectCallback().getResourceId( + resValue.getResourceType(), resValue.getName()); } if (idValue != null) { return idValue.intValue(); } - mContext.getLogger().warning(String.format( - "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE, + String.format( + "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index]), + resValue); + return defValue; } @@ -657,28 +695,17 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public Drawable getDrawable(int index) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return null; } - IResourceValue value = mData[index]; + ResourceValue value = mResourceData[index]; String stringValue = value.getValue(); - if (stringValue == null || BridgeConstants.REFERENCE_NULL.equals(stringValue)) { + if (stringValue == null || RenderResources.REFERENCE_NULL.equals(stringValue)) { return null; } - Drawable d = ResourceHelper.getDrawable(value, mContext, mData[index].isFramework()); - - if (d != null) { - return d; - } - - // looks like we were unable to resolve the drawable - mContext.getLogger().warning(String.format( - "Unable to resolve drawable \"%1$s\" in attribute \"%2$s\"", stringValue, - mNames[index])); - - return null; + return ResourceHelper.getDrawable(value, mContext); } @@ -694,18 +721,23 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence[] getTextArray(int index) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return null; } - String value = mData[index].getValue(); + String value = mResourceData[index].getValue(); if (value != null) { + if (RenderResources.REFERENCE_NULL.equals(value)) { + return null; + } + return new CharSequence[] { value }; } - mContext.getLogger().warning(String.format( - String.format("Unknown value for getTextArray(%d) => %s", //DEBUG - index, mData[index].getName()))); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + String.format("Unknown value for getTextArray(%d) => %s", //DEBUG + index, mResourceData[index].getName())), null /*data*/); return null; } @@ -721,11 +753,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean getValue(int index, TypedValue outValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return false; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); return ResourceHelper.stringToFloat(s, outValue); } @@ -739,7 +771,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean hasValue(int index) { - return mData[index] != null; + return mResourceData[index] != null; } /** @@ -786,6 +818,6 @@ public final class BridgeTypedArray extends TypedArray { @Override public String toString() { - return mData.toString(); + return Arrays.toString(mResourceData); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java new file mode 100644 index 0000000..a8da377 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java @@ -0,0 +1,97 @@ +/* + * 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.android; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.view.DragEvent; +import android.view.IWindow; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View.AttachInfo; + +/** + * Implementation of {@link IWindow} to pass to the {@link AttachInfo}. + */ +public final class BridgeWindow implements IWindow { + + public void dispatchAppVisibility(boolean arg0) throws RemoteException { + // pass for now. + } + + public void dispatchGetNewSurface() throws RemoteException { + // pass for now. + } + + public void dispatchKey(KeyEvent arg0) throws RemoteException { + // pass for now. + } + + public void dispatchPointer(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { + // pass for now. + } + + public void dispatchTrackball(MotionEvent arg0, long arg1, boolean arg2) + throws RemoteException { + // pass for now. + } + + public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2) + throws RemoteException { + // pass for now. + } + + public void resized(int arg0, int arg1, Rect arg2, Rect arg3, boolean arg4, Configuration arg5) + throws RemoteException { + // pass for now. + } + + public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException { + // pass for now. + } + + public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, + boolean sync) { + // pass for now. + } + + public void dispatchWallpaperCommand(String action, int x, int y, + int z, Bundle extras, boolean sync) { + // pass for now. + } + + public void closeSystemDialogs(String reason) { + // pass for now. + } + + public void dispatchDragEvent(DragEvent event) { + // pass for now. + } + + public void dispatchSystemUiVisibilityChanged(int visibility) { + // pass for now. + } + + public IBinder asBinder() { + // pass for now. + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java new file mode 100644 index 0000000..8422d48 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -0,0 +1,169 @@ +/* + * 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.android; + +import android.content.ClipData; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.IWindow; +import android.view.IWindowSession; +import android.view.InputChannel; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceView; +import android.view.WindowManager.LayoutParams; + +/** + * Implementation of {@link IWindowSession} so that mSession is not null in + * the {@link SurfaceView}. + */ +public final class BridgeWindowSession implements IWindowSession { + + public int add(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3, + InputChannel outInputchannel) + throws RemoteException { + // pass for now. + return 0; + } + + public int addWithoutInputChannel(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3) + throws RemoteException { + // pass for now. + return 0; + } + + public void finishDrawing(IWindow arg0) throws RemoteException { + // pass for now. + } + + public void finishKey(IWindow arg0) throws RemoteException { + // pass for now. + } + + public boolean getInTouchMode() throws RemoteException { + // pass for now. + return false; + } + + public boolean performHapticFeedback(IWindow window, int effectId, boolean always) { + // pass for now. + return false; + } + + public MotionEvent getPendingPointerMove(IWindow arg0) throws RemoteException { + // pass for now. + return null; + } + + public MotionEvent getPendingTrackballMove(IWindow arg0) throws RemoteException { + // pass for now. + return null; + } + + public int relayout(IWindow arg0, LayoutParams arg1, int arg2, int arg3, int arg4, + boolean arg4_5, Rect arg5, Rect arg6, Rect arg7, Configuration arg7b, Surface arg8) + throws RemoteException { + // pass for now. + return 0; + } + + public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { + // pass for now. + } + + public void remove(IWindow arg0) throws RemoteException { + // pass for now. + } + + public void setInTouchMode(boolean arg0) throws RemoteException { + // pass for now. + } + + public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException { + // pass for now. + } + + public void setInsets(IWindow window, int touchable, Rect contentInsets, + Rect visibleInsets, Region touchableRegion) { + // pass for now. + } + + public IBinder prepareDrag(IWindow window, int flags, + int thumbnailWidth, int thumbnailHeight, Surface outSurface) + throws RemoteException { + // pass for now + return null; + } + + public boolean performDrag(IWindow window, IBinder dragToken, + float touchX, float touchY, float thumbCenterX, float thumbCenterY, + ClipData data) + throws RemoteException { + // pass for now + return false; + } + + public void reportDropResult(IWindow window, boolean consumed) throws RemoteException { + // pass for now + } + + public void dragRecipientEntered(IWindow window) throws RemoteException { + // pass for now + } + + public void dragRecipientExited(IWindow window) throws RemoteException { + // pass for now + } + + public void setWallpaperPosition(IBinder window, float x, float y, + float xStep, float yStep) { + // pass for now. + } + + public void wallpaperOffsetsComplete(IBinder window) { + // pass for now. + } + + public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, + int z, Bundle extras, boolean sync) { + // pass for now. + return null; + } + + public void wallpaperCommandComplete(IBinder window, Bundle result) { + // pass for now. + } + + public void closeSystemDialogs(String reason) { + // pass for now. + } + + public IBinder asBinder() { + // pass for now. + return null; + } + + public IBinder prepareDrag(IWindow arg0, boolean arg1, int arg2, int arg3, Surface arg4) + throws RemoteException { + // TODO Auto-generated method stub + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java index d842a66..2f54ae6 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlBlockParser.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.IXmlPullParser; + +import com.android.ide.common.rendering.api.ILayoutPullParser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -36,14 +37,15 @@ import java.io.Reader; */ public class BridgeXmlBlockParser implements XmlResourceParser { - private XmlPullParser mParser; - private XmlPullAttributes mAttrib; + private final XmlPullParser mParser; + private final XmlPullAttributes mAttrib; + private final BridgeContext mContext; + private final boolean mPlatformFile; private boolean mStarted = false; - private boolean mDecNextDepth = false; - private int mDepth = 0; private int mEventType = START_DOCUMENT; - private final boolean mPlatformFile; + + private boolean mPopped = true; // default to true in case it's not pushed. /** * Builds a {@link BridgeXmlBlockParser}. @@ -53,25 +55,45 @@ public class BridgeXmlBlockParser implements XmlResourceParser { */ public BridgeXmlBlockParser(XmlPullParser parser, BridgeContext context, boolean platformFile) { mParser = parser; + mContext = context; mPlatformFile = platformFile; mAttrib = new BridgeXmlPullAttributes(parser, context, mPlatformFile); + + if (mContext != null) { + mContext.pushParser(this); + mPopped = false; + } } - + public boolean isPlatformFile() { return mPlatformFile; } - public Object getViewKey() { - if (mParser instanceof IXmlPullParser) { - return ((IXmlPullParser)mParser).getViewKey(); + public ILayoutPullParser getParser(String layoutName) { + if (mParser instanceof ILayoutPullParser) { + return ((ILayoutPullParser)mParser).getParser(layoutName); + } + + return null; + } + + public Object getViewCookie() { + if (mParser instanceof ILayoutPullParser) { + return ((ILayoutPullParser)mParser).getViewCookie(); } return null; } - - + + public void ensurePopped() { + if (mContext != null && mPopped == false) { + mContext.popParser(); + mPopped = true; + } + } + // ------- XmlResourceParser implementation - + public void setFeature(String name, boolean state) throws XmlPullParserException { if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) { @@ -145,7 +167,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { } public int getDepth() { - return mDepth; + return mParser.getDepth(); } public String getText() { @@ -236,17 +258,10 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return START_DOCUMENT; } int ev = mParser.next(); - if (mDecNextDepth) { - mDepth--; - mDecNextDepth = false; - } - switch (ev) { - case START_TAG: - mDepth++; - break; - case END_TAG: - mDecNextDepth = true; - break; + + if (ev == END_TAG && mParser.getDepth() == 1) { + // done with parser remove it from the context stack. + ensurePopped(); } mEventType = ev; return ev; @@ -301,7 +316,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { // AttributeSet implementation - + public void close() { // pass } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlPullAttributes.java index d145ff6..ba856e0 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlPullAttributes.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlPullAttributes.java @@ -14,9 +14,13 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.IResourceValue; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.resources.ResourceType; import org.xmlpull.v1.XmlPullParser; @@ -55,7 +59,7 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { String ns = mParser.getAttributeNamespace(index); if (BridgeConstants.NS_RESOURCES.equals(ns)) { - Integer v = Bridge.getResourceValue(BridgeConstants.RES_ATTR, name); + Integer v = Bridge.getResourceId(ResourceType.ATTR, name); if (v != null) { return v.intValue(); } @@ -66,8 +70,7 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { // this is not an attribute in the android namespace, we query the customviewloader, if // the namespaces match. if (mContext.getProjectCallback().getNamespace().equals(ns)) { - Integer v = mContext.getProjectCallback().getResourceValue(BridgeConstants.RES_ATTR, - name); + Integer v = mContext.getProjectCallback().getResourceId(ResourceType.ATTR, name); if (v != null) { return v.intValue(); } @@ -100,16 +103,17 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { private int resolveResourceValue(String value, int defaultValue) { // now look for this particular value - IResourceValue resource = mContext.resolveResValue( - mContext.findResValue(value, mPlatformFile)); + RenderResources resources = mContext.getRenderResources(); + ResourceValue resource = resources.resolveResValue( + resources.findResValue(value, mPlatformFile)); if (resource != null) { Integer id = null; if (mPlatformFile || resource.isFramework()) { - id = Bridge.getResourceValue(resource.getType(), resource.getName()); + id = Bridge.getResourceId(resource.getResourceType(), resource.getName()); } else { - id = mContext.getProjectCallback().getResourceValue( - resource.getType(), resource.getName()); + id = mContext.getProjectCallback().getResourceId( + resource.getResourceType(), resource.getName()); } if (id != null) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java new file mode 100644 index 0000000..0c4b0d3 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2011 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.bars; + +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.resources.Density; +import com.android.resources.ResourceType; + +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.Bitmap_Delegate; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Base "bar" class for the window decor around the the edited layout. + * This is basically an horizontal layout that loads a given layout on creation (it is read + * through {@link Class#getResourceAsStream(String)}). + * + * The given layout should be a merge layout so that all the children belong to this class directly. + * + * It also provides a few utility methods to configure the content of the layout. + */ +abstract class CustomBar extends LinearLayout { + + protected abstract TextView getStyleableTextView(); + + protected CustomBar(Context context, Density density, String layoutPath) + throws XmlPullParserException { + super(context); + setOrientation(LinearLayout.HORIZONTAL); + setGravity(Gravity.CENTER_VERTICAL); + + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + KXmlParser parser = new KXmlParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput( + getClass().getResourceAsStream(layoutPath), + "UTF8"); + + BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( + parser, (BridgeContext) context, false /*platformFile*/); + + try { + inflater.inflate(bridgeParser, this, true); + } finally { + bridgeParser.ensurePopped(); + } + } + + private InputStream getIcon(String iconName, Density[] densityInOut, String[] pathOut, + boolean tryOtherDensities) { + // current density + Density density = densityInOut[0]; + + // bitmap url relative to this class + pathOut[0] = "/bars/" + density.getResourceValue() + "/" + iconName; + + InputStream stream = getClass().getResourceAsStream(pathOut[0]); + if (stream == null && tryOtherDensities) { + for (Density d : Density.values()) { + if (d != density) { + densityInOut[0] = d; + stream = getIcon(iconName, densityInOut, pathOut, false /*tryOtherDensities*/); + if (stream != null) { + return stream; + } + } + } + } + + return stream; + } + + protected void loadIcon(int index, String iconName, Density density) { + View child = getChildAt(index); + if (child instanceof ImageView) { + ImageView imageView = (ImageView) child; + + String[] pathOut = new String[1]; + Density[] densityInOut = new Density[] { density }; + InputStream stream = getIcon(iconName, densityInOut, pathOut, + true /*tryOtherDensities*/); + density = densityInOut[0]; + + if (stream != null) { + // look for a cached bitmap + Bitmap bitmap = Bridge.getCachedBitmap(pathOut[0], true /*isFramework*/); + if (bitmap == null) { + try { + bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density); + Bridge.setCachedBitmap(pathOut[0], bitmap, true /*isFramework*/); + } catch (IOException e) { + return; + } + } + + if (bitmap != null) { + BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), + bitmap); + imageView.setBackgroundDrawable(drawable); + } + } + } + } + + protected void loadIcon(int index, String iconReference) { + ResourceValue value = getResourceValue(iconReference); + if (value != null) { + loadIcon(index, value); + } + } + + protected Drawable loadIcon(int index, ResourceType type, String name) { + BridgeContext bridgeContext = (BridgeContext) mContext; + RenderResources res = bridgeContext.getRenderResources(); + + // find the resource + ResourceValue value = res.getFrameworkResource(type, name); + + // resolve it if needed + value = res.resolveResValue(value); + return loadIcon(index, value); + } + + private Drawable loadIcon(int index, ResourceValue value) { + View child = getChildAt(index); + if (child instanceof ImageView) { + ImageView imageView = (ImageView) child; + + Drawable drawable = ResourceHelper.getDrawable( + value, (BridgeContext) mContext); + if (drawable != null) { + imageView.setBackgroundDrawable(drawable); + } + + return drawable; + } + + return null; + } + + protected TextView setText(int index, String stringReference) { + View child = getChildAt(index); + if (child instanceof TextView) { + TextView textView = (TextView) child; + ResourceValue value = getResourceValue(stringReference); + if (value != null) { + textView.setText(value.getValue()); + } else { + textView.setText(stringReference); + } + return textView; + } + + return null; + } + + protected void setStyle(String themeEntryName) { + + BridgeContext bridgeContext = (BridgeContext) mContext; + RenderResources res = bridgeContext.getRenderResources(); + + ResourceValue value = res.findItemInTheme(themeEntryName); + value = res.resolveResValue(value); + + if (value instanceof StyleResourceValue == false) { + return; + } + + StyleResourceValue style = (StyleResourceValue) value; + + // get the background + ResourceValue backgroundValue = res.findItemInStyle(style, "background"); + backgroundValue = res.resolveResValue(backgroundValue); + if (backgroundValue != null) { + Drawable d = ResourceHelper.getDrawable(backgroundValue, bridgeContext); + if (d != null) { + setBackgroundDrawable(d); + } + } + + TextView textView = getStyleableTextView(); + if (textView != null) { + // get the text style + ResourceValue textStyleValue = res.findItemInStyle(style, "titleTextStyle"); + textStyleValue = res.resolveResValue(textStyleValue); + if (textStyleValue instanceof StyleResourceValue) { + StyleResourceValue textStyle = (StyleResourceValue) textStyleValue; + + ResourceValue textSize = res.findItemInStyle(textStyle, "textSize"); + textSize = res.resolveResValue(textSize); + + if (textSize != null) { + TypedValue out = new TypedValue(); + if (ResourceHelper.stringToFloat(textSize.getValue(), out)) { + textView.setTextSize( + out.getDimension(bridgeContext.getResources().mMetrics)); + } + } + + + ResourceValue textColor = res.findItemInStyle(textStyle, "textColor"); + textColor = res.resolveResValue(textColor); + if (textColor != null) { + ColorStateList stateList = ResourceHelper.getColorStateList( + textColor, bridgeContext); + if (stateList != null) { + textView.setTextColor(stateList); + } + } + } + } + } + + private ResourceValue getResourceValue(String reference) { + BridgeContext bridgeContext = (BridgeContext) mContext; + RenderResources res = bridgeContext.getRenderResources(); + + // find the resource + ResourceValue value = res.findResValue(reference, false /*isFramework*/); + + // resolve it if needed + return res.resolveResValue(value); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java new file mode 100644 index 0000000..3af4e3a --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 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.bars; + +import com.android.resources.Density; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.widget.TextView; + +public class FakeActionBar extends CustomBar { + + private TextView mTextView; + + public FakeActionBar(Context context, Density density, String label, String icon) + throws XmlPullParserException { + super(context, density, "/bars/action_bar.xml"); + + // Cannot access the inside items through id because no R.id values have been + // created for them. + // We do know the order though. + loadIcon(0, icon); + mTextView = setText(1, label); + + setStyle("actionBarStyle"); + } + + @Override + protected TextView getStyleableTextView() { + return mTextView; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/PhoneSystemBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/PhoneSystemBar.java new file mode 100644 index 0000000..9fab51a --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/PhoneSystemBar.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 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.bars; + +import com.android.resources.Density; +import com.android.resources.ResourceType; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LevelListDrawable; +import android.view.Gravity; +import android.widget.TextView; + +public class PhoneSystemBar extends CustomBar { + + public PhoneSystemBar(Context context, Density density) throws XmlPullParserException { + super(context, density, "/bars/phone_system_bar.xml"); + + setGravity(mGravity | Gravity.RIGHT); + setBackgroundColor(0xFF000000); + + // Cannot access the inside items through id because no R.id values have been + // created for them. + // We do know the order though. + // 0 is the spacer + loadIcon(1, "stat_sys_wifi_signal_4_fully.png", density); + Drawable drawable = loadIcon(2, ResourceType.DRAWABLE, "stat_sys_battery_charge"); + if (drawable instanceof LevelListDrawable) { + ((LevelListDrawable) drawable).setLevel(100); + } + } + + @Override + protected TextView getStyleableTextView() { + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TabletSystemBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TabletSystemBar.java new file mode 100644 index 0000000..5ca68fa --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TabletSystemBar.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 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.bars; + +import com.android.resources.Density; +import com.android.resources.ResourceType; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LevelListDrawable; +import android.widget.TextView; + +public class TabletSystemBar extends CustomBar { + + public TabletSystemBar(Context context, Density density) throws XmlPullParserException { + super(context, density, "/bars/tablet_system_bar.xml"); + + setBackgroundColor(0xFF000000); + + // Cannot access the inside items through id because no R.id values have been + // created for them. + // We do know the order though. + loadIcon(0, "ic_sysbar_back_default.png", density); + loadIcon(1, "ic_sysbar_home_default.png", density); + loadIcon(2, "ic_sysbar_recent_default.png", density); + // 3 is the spacer + loadIcon(4, "stat_sys_wifi_signal_4_fully.png", density); + Drawable drawable = loadIcon(5, ResourceType.DRAWABLE, "stat_sys_battery_charge"); + if (drawable instanceof LevelListDrawable) { + ((LevelListDrawable) drawable).setLevel(100); + } + } + + @Override + protected TextView getStyleableTextView() { + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java new file mode 100644 index 0000000..d7401d9 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 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.bars; + +import com.android.resources.Density; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.widget.TextView; + +public class TitleBar extends CustomBar { + + private TextView mTextView; + + public TitleBar(Context context, Density density, String label) + throws XmlPullParserException { + super(context, density, "/bars/title_bar.xml"); + + // Cannot access the inside items through id because no R.id values have been + // created for them. + // We do know the order though. + mTextView = setText(0, label); + + setStyle("windowTitleBackgroundStyle"); + } + + @Override + protected TextView getStyleableTextView() { + return mTextView; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java new file mode 100644 index 0000000..4c18656 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java @@ -0,0 +1,170 @@ +/* + * 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.impl; + +import com.android.ide.common.rendering.api.IAnimationListener; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.Result.Status; +import com.android.layoutlib.bridge.Bridge; + +import android.animation.ValueAnimator; +import android.os.Handler; +import android.os.Handler_Delegate; +import android.os.Message; +import android.os.Handler_Delegate.IHandlerCallback; + +import java.util.PriorityQueue; +import java.util.Queue; + +/** + * Abstract animation thread. + * <p/> + * This does not actually start an animation, instead it fakes a looper that will play whatever + * animation is sending messages to its own {@link Handler}. + * <p/> + * Classes should implement {@link #preAnimation()} and {@link #postAnimation()}. + * <p/> + * If {@link #preAnimation()} does not start an animation somehow then the thread doesn't do + * anything. + * + */ +public abstract class AnimationThread extends Thread { + + private static class MessageBundle implements Comparable<MessageBundle> { + final Handler mTarget; + final Message mMessage; + final long mUptimeMillis; + + MessageBundle(Handler target, Message message, long uptimeMillis) { + mTarget = target; + mMessage = message; + mUptimeMillis = uptimeMillis; + } + + public int compareTo(MessageBundle bundle) { + if (mUptimeMillis < bundle.mUptimeMillis) { + return -1; + } + return 1; + } + } + + private final RenderSessionImpl mSession; + + private Queue<MessageBundle> mQueue = new PriorityQueue<MessageBundle>(); + private final IAnimationListener mListener; + + public AnimationThread(RenderSessionImpl scene, String threadName, + IAnimationListener listener) { + super(threadName); + mSession = scene; + mListener = listener; + } + + public abstract Result preAnimation(); + public abstract void postAnimation(); + + @Override + public void run() { + Bridge.prepareThread(); + try { + Handler_Delegate.setCallback(new IHandlerCallback() { + public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) { + if (msg.what == ValueAnimator.ANIMATION_START || + msg.what == ValueAnimator.ANIMATION_FRAME) { + mQueue.add(new MessageBundle(handler, msg, uptimeMillis)); + } else { + // just ignore. + } + } + }); + + // call out to the pre-animation work, which should start an animation or more. + Result result = preAnimation(); + if (result.isSuccess() == false) { + mListener.done(result); + } + + // loop the animation + RenderSession session = mSession.getSession(); + do { + // check early. + if (mListener.isCanceled()) { + break; + } + + // get the next message. + MessageBundle bundle = mQueue.poll(); + if (bundle == null) { + break; + } + + // sleep enough for this bundle to be on time + long currentTime = System.currentTimeMillis(); + if (currentTime < bundle.mUptimeMillis) { + try { + sleep(bundle.mUptimeMillis - currentTime); + } catch (InterruptedException e) { + // FIXME log/do something/sleep again? + e.printStackTrace(); + } + } + + // check after sleeping. + if (mListener.isCanceled()) { + break; + } + + // ready to do the work, acquire the scene. + result = mSession.acquire(250); + if (result.isSuccess() == false) { + mListener.done(result); + return; + } + + // process the bundle. If the animation is not finished, this will enqueue + // the next message, so mQueue will have another one. + try { + // check after acquiring in case it took a while. + if (mListener.isCanceled()) { + break; + } + + bundle.mTarget.handleMessage(bundle.mMessage); + if (mSession.render(false /*freshRender*/).isSuccess()) { + mListener.onNewFrame(session); + } + } finally { + mSession.release(); + } + } while (mListener.isCanceled() == false && mQueue.size() > 0); + + mListener.done(Status.SUCCESS.createResult()); + + } catch (Throwable throwable) { + // can't use Bridge.getLog() as the exception might be thrown outside + // of an acquire/release block. + mListener.done(Status.ERROR_UNKNOWN.createResult("Error playing animation", throwable)); + + } finally { + postAnimation(); + Handler_Delegate.setCallback(null); + Bridge.cleanupThread(); + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java new file mode 100644 index 0000000..ae1217d --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java @@ -0,0 +1,146 @@ +/* + * 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.impl; + +import com.android.layoutlib.bridge.util.Debug; +import com.android.layoutlib.bridge.util.SparseWeakArray; + +import android.util.SparseArray; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * 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 #addNewDelegate(Object)} does the same. + * + * The JNI references are counted, so we do the same through a {@link WeakReference}. Because + * the Java object needs to count as a reference (even though it only holds an int), we use the + * following mechanism: + * + * - {@link #addNewDelegate(Object)} and {@link #removeJavaReferenceFor(int)} adds and removes + * the delegate to/from a list. This list hold the reference and prevents the GC from reclaiming + * the delegate. + * + * - {@link #addNewDelegate(Object)} also adds the delegate to a {@link SparseArray} that holds a + * {@link WeakReference} to the delegate. This allows the delegate to be deleted automatically + * when nothing references it. This means that any class that holds a delegate (except for the + * Java main class) must not use the int but the Delegate class instead. The integers must + * only be used in the API between the main Java class and the Delegate. + * + * @param <T> the delegate class to manage + */ +public final class DelegateManager<T> { + private final Class<T> mClass; + private final SparseWeakArray<T> mDelegates = new SparseWeakArray<T>(); + /** list used to store delegates when their main object holds a reference to them. + * This is to ensure that the WeakReference in the SparseWeakArray doesn't get GC'ed + * @see #addNewDelegate(Object) + * @see #removeJavaReferenceFor(int) + */ + private final List<T> mJavaReferences = new ArrayList<T>(); + private int mDelegateCounter = 0; + + public DelegateManager(Class<T> theClass) { + mClass = theClass; + } + + /** + * Returns the delegate from the given native int. + * <p> + * If the int is zero, then this will always return null. + * <p> + * If the int is non zero and the delegate is not found, this will throw an assert. + * + * @param native_object the native int. + * @return the delegate or null if not found. + */ + public T getDelegate(int native_object) { + if (native_object > 0) { + T delegate = mDelegates.get(native_object); + + if (Debug.DEBUG) { + if (delegate == null) { + System.out.println("Unknown " + mClass.getSimpleName() + " with int " + + native_object); + } + } + + assert delegate != null; + return delegate; + } + return null; + } + + /** + * 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 addNewDelegate(T newDelegate) { + int native_object = ++mDelegateCounter; + mDelegates.put(native_object, newDelegate); + assert !mJavaReferences.contains(newDelegate); + mJavaReferences.add(newDelegate); + + if (Debug.DEBUG) { + System.out.println("New " + mClass.getSimpleName() + " with int " + native_object); + } + + return native_object; + } + + /** + * Removes the main reference on the given delegate. + * @param native_object the native integer representing the delegate. + */ + public void removeJavaReferenceFor(int native_object) { + T delegate = getDelegate(native_object); + + if (Debug.DEBUG) { + System.out.println("Removing main Java ref on " + mClass.getSimpleName() + + " with int " + native_object); + } + + mJavaReferences.remove(delegate); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java index 801503b..f62fad2 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.impl; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -134,6 +134,15 @@ public final class FontLoader { return mFallBackFonts; } + /** + * Returns a {@link Font} object given a family name and a style value (constant in + * {@link Typeface}). + * @param family the family name + * @param style a 1-item array containing the requested style. Based on the font being read + * the actual style may be different. The array contains the actual style after + * the method returns. + * @return the font object or null if no match could be found. + */ public synchronized Font getFont(String family, int[] style) { if (family == null) { return null; @@ -154,7 +163,7 @@ public final class FontLoader { mTtfToFontMap.put(ttf, styleMap); } - Font f = styleMap.get(style); + Font f = styleMap.get(style[0]); if (f != null) { return f; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java new file mode 100644 index 0000000..21d6b1a --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java @@ -0,0 +1,803 @@ +/* + * 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.impl; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; + +import android.graphics.Bitmap_Delegate; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint_Delegate; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.graphics.Region_Delegate; +import android.graphics.Shader_Delegate; +import android.graphics.Xfermode_Delegate; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; + +/** + * Class representing a graphics context snapshot, as well as a context stack as a linked list. + * <p> + * This is based on top of {@link Graphics2D} but can operate independently if none are available + * yet when setting transforms and clip information. + * <p> + * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and + * {@link #draw(Drawable, Paint_Delegate)} + * + * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through + * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer} + * for each layer. Doing a save() will duplicate this list so that each graphics2D object + * ({@link Layer#getGraphics()}) is configured only for the new snapshot. + */ +public class GcSnapshot { + + private final GcSnapshot mPrevious; + private final int mFlags; + + /** list of layers. The first item in the list is always the */ + private final ArrayList<Layer> mLayers = new ArrayList<Layer>(); + + /** temp transform in case transformation are set before a Graphics2D exists */ + private AffineTransform mTransform = null; + /** temp clip in case clipping is set before a Graphics2D exists */ + private Area mClip = null; + + // local layer data + /** a local layer created with {@link Canvas#saveLayer(RectF, Paint, int)}. + * If this is null, this does not mean there's no layer, just that the snapshot is not the + * one that created the layer. + * @see #getLayerSnapshot() + */ + private final Layer mLocalLayer; + private final Paint_Delegate mLocalLayerPaint; + private final Rect mLayerBounds; + + public interface Drawable { + void draw(Graphics2D graphics, Paint_Delegate paint); + } + + /** + * Class containing information about a layer. + * + * This contains graphics, bitmap and layer information. + */ + private static class Layer { + private final Graphics2D mGraphics; + private final Bitmap_Delegate mBitmap; + private final BufferedImage mImage; + /** the flags that were used to configure the layer. This is never changed, and passed + * as is when {@link #makeCopy()} is called */ + private final int mFlags; + /** the original content of the layer when the next object was created. This is not + * passed in {@link #makeCopy()} and instead is recreated when a new layer is added + * (depending on its flags) */ + private BufferedImage mOriginalCopy; + + /** + * Creates a layer with a graphics and a bitmap. This is only used to create + * the base layer. + * + * @param graphics the graphics + * @param bitmap the bitmap + */ + Layer(Graphics2D graphics, Bitmap_Delegate bitmap) { + mGraphics = graphics; + mBitmap = bitmap; + mImage = mBitmap.getImage(); + mFlags = 0; + } + + /** + * Creates a layer with a graphics and an image. If the image belongs to a + * {@link Bitmap_Delegate} (case of the base layer), then + * {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used. + * + * @param graphics the graphics the new graphics for this layer + * @param image the image the image from which the graphics came + * @param flags the flags that were used to save this layer + */ + Layer(Graphics2D graphics, BufferedImage image, int flags) { + mGraphics = graphics; + mBitmap = null; + mImage = image; + mFlags = flags; + } + + /** The Graphics2D, guaranteed to be non null */ + Graphics2D getGraphics() { + return mGraphics; + } + + /** The BufferedImage, guaranteed to be non null */ + BufferedImage getImage() { + return mImage; + } + + /** Returns the layer save flags. This is only valid for additional layers. + * For the base layer this will always return 0; + * For a given layer, all further copies of this {@link Layer} object in new snapshots + * will always return the same value. + */ + int getFlags() { + return mFlags; + } + + Layer makeCopy() { + if (mBitmap != null) { + return new Layer((Graphics2D) mGraphics.create(), mBitmap); + } + + return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags); + } + + /** sets an optional copy of the original content to be used during restore */ + void setOriginalCopy(BufferedImage image) { + mOriginalCopy = image; + } + + BufferedImage getOriginalCopy() { + return mOriginalCopy; + } + + void change() { + if (mBitmap != null) { + mBitmap.change(); + } + } + + /** + * Sets the clip for the graphics2D object associated with the layer. + * This should be used over the normal Graphics2D setClip method. + * + * @param clipShape the shape to use a the clip shape. + */ + void setClip(Shape clipShape) { + // because setClip is only guaranteed to work with rectangle shape, + // first reset the clip to max and then intersect the current (empty) + // clip with the shap. + mGraphics.setClip(null); + mGraphics.clip(clipShape); + } + + /** + * Clips the layer with the given shape. This performs an intersect between the current + * clip shape and the given shape. + * @param shape the new clip shape. + */ + public void clip(Shape shape) { + mGraphics.clip(shape); + } + } + + /** + * Creates the root snapshot associating it with a given bitmap. + * <p> + * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be + * called before the snapshot can be used to draw. Transform and clip operations are permitted + * before. + * + * @param image the image to associate to the snapshot or null. + * @return the root snapshot + */ + public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) { + GcSnapshot snapshot = new GcSnapshot(); + if (bitmap != null) { + snapshot.setBitmap(bitmap); + } + + return snapshot; + } + + /** + * Saves the current state according to the given flags and returns the new current snapshot. + * <p/> + * This is the equivalent of {@link Canvas#save(int)} + * + * @param flags the save flags. + * @return the new snapshot + * + * @see Canvas#save(int) + */ + public GcSnapshot save(int flags) { + return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags); + } + + /** + * Saves the current state and creates a new layer, and returns the new current snapshot. + * <p/> + * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)} + * + * @param layerBounds the layer bounds + * @param paint the Paint information used to blit the layer back into the layers underneath + * upon restore + * @param flags the save flags. + * @return the new snapshot + * + * @see Canvas#saveLayer(RectF, Paint, int) + */ + public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) { + return new GcSnapshot(this, layerBounds, paint, flags); + } + + /** + * Creates the root snapshot. + * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible. + */ + private GcSnapshot() { + mPrevious = null; + mFlags = 0; + mLocalLayer = null; + mLocalLayerPaint = null; + mLayerBounds = null; + } + + /** + * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored + * into the main graphics when {@link #restore()} is called. + * + * @param previous the previous snapshot head. + * @param layerBounds the region of the layer. Optional, if null, this is a normal save() + * @param paint the Paint information used to blit the layer back into the layers underneath + * upon restore + * @param flags the flags regarding what should be saved. + */ + private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) { + assert previous != null; + mPrevious = previous; + mFlags = flags; + + // make a copy of the current layers before adding the new one. + // This keeps the same BufferedImage reference but creates new Graphics2D for this + // snapshot. + // It does not copy whatever original copy the layers have, as they will be done + // only if the new layer doesn't clip drawing to itself. + for (Layer layer : mPrevious.mLayers) { + mLayers.add(layer.makeCopy()); + } + + if (layerBounds != null) { + // get the current transform + AffineTransform matrix = mLayers.get(0).getGraphics().getTransform(); + + // transform the layerBounds with the current transform and stores it into a int rect + RectF rect2 = new RectF(); + mapRect(matrix, rect2, layerBounds); + mLayerBounds = new Rect(); + rect2.round(mLayerBounds); + + // get the base layer (always at index 0) + Layer baseLayer = mLayers.get(0); + + // create the image for the layer + BufferedImage layerImage = new BufferedImage( + baseLayer.getImage().getWidth(), + baseLayer.getImage().getHeight(), + (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ? + BufferedImage.TYPE_INT_ARGB : + BufferedImage.TYPE_INT_RGB); + + // create a graphics for it so that drawing can be done. + Graphics2D layerGraphics = layerImage.createGraphics(); + + // because this layer inherits the current context for transform and clip, + // set them to one from the base layer. + AffineTransform currentMtx = baseLayer.getGraphics().getTransform(); + layerGraphics.setTransform(currentMtx); + + // create a new layer for this new layer and add it to the list at the end. + mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags)); + + // set the clip on it. + Shape currentClip = baseLayer.getGraphics().getClip(); + mLocalLayer.setClip(currentClip); + + // if the drawing is not clipped to the local layer only, we save the current content + // of all other layers. We are only interested in the part that will actually + // be drawn, so we create as small bitmaps as we can. + // This is so that we can erase the drawing that goes in the layers below that will + // be coming from the layer itself. + if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) { + int w = mLayerBounds.width(); + int h = mLayerBounds.height(); + for (int i = 0 ; i < mLayers.size() - 1 ; i++) { + Layer layer = mLayers.get(i); + BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = image.createGraphics(); + graphics.drawImage(layer.getImage(), + 0, 0, w, h, + mLayerBounds.left, mLayerBounds.top, + mLayerBounds.right, mLayerBounds.bottom, + null); + graphics.dispose(); + layer.setOriginalCopy(image); + } + } + } else { + mLocalLayer = null; + mLayerBounds = null; + } + + mLocalLayerPaint = paint; + } + + public void dispose() { + for (Layer layer : mLayers) { + layer.getGraphics().dispose(); + } + + if (mPrevious != null) { + mPrevious.dispose(); + } + } + + /** + * Restores the top {@link GcSnapshot}, and returns the next one. + */ + public GcSnapshot restore() { + return doRestore(); + } + + /** + * Restores the {@link GcSnapshot} to <var>saveCount</var>. + * @param saveCount the saveCount or -1 to only restore 1. + * + * @return the new head of the Gc snapshot stack. + */ + public GcSnapshot restoreTo(int saveCount) { + return doRestoreTo(size(), saveCount); + } + + public int size() { + if (mPrevious != null) { + return mPrevious.size() + 1; + } + + return 1; + } + + /** + * Link the snapshot to a Bitmap_Delegate. + * <p/> + * This is only for the case where the snapshot was created with a null image when calling + * {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to + * a previous snapshot. + * <p/> + * If any transform or clip information was set before, they are put into the Graphics object. + * @param bitmap the bitmap to link to. + */ + public void setBitmap(Bitmap_Delegate bitmap) { + // create a new Layer for the bitmap. This will be the base layer. + Graphics2D graphics2D = bitmap.getImage().createGraphics(); + Layer baseLayer = new Layer(graphics2D, bitmap); + + // Set the current transform and clip which can either come from mTransform/mClip if they + // were set when there was no bitmap/layers or from the current base layers if there is + // one already. + + graphics2D.setTransform(getTransform()); + // reset mTransform in case there was one. + mTransform = null; + + baseLayer.setClip(getClip()); + // reset mClip in case there was one. + mClip = null; + + // replace whatever current layers we have with this. + mLayers.clear(); + mLayers.add(baseLayer); + + } + + public void translate(float dx, float dy) { + if (mLayers.size() > 0) { + for (Layer layer : mLayers) { + layer.getGraphics().translate(dx, dy); + } + } else { + if (mTransform == null) { + mTransform = new AffineTransform(); + } + mTransform.translate(dx, dy); + } + } + + public void rotate(double radians) { + if (mLayers.size() > 0) { + for (Layer layer : mLayers) { + layer.getGraphics().rotate(radians); + } + } else { + if (mTransform == null) { + mTransform = new AffineTransform(); + } + mTransform.rotate(radians); + } + } + + public void scale(float sx, float sy) { + if (mLayers.size() > 0) { + for (Layer layer : mLayers) { + layer.getGraphics().scale(sx, sy); + } + } else { + if (mTransform == null) { + mTransform = new AffineTransform(); + } + mTransform.scale(sx, sy); + } + } + + public AffineTransform getTransform() { + if (mLayers.size() > 0) { + // all graphics2D in the list have the same transform + return mLayers.get(0).getGraphics().getTransform(); + } else { + if (mTransform == null) { + mTransform = new AffineTransform(); + } + return mTransform; + } + } + + public void setTransform(AffineTransform transform) { + if (mLayers.size() > 0) { + for (Layer layer : mLayers) { + layer.getGraphics().setTransform(transform); + } + } else { + if (mTransform == null) { + mTransform = new AffineTransform(); + } + mTransform.setTransform(transform); + } + } + + public boolean clip(Shape shape, int regionOp) { + // Simple case of intersect with existing layers. + // Because Graphics2D#setClip works a bit peculiarly, we optimize + // the case of clipping by intersection, as it's supported natively. + if (regionOp == Region.Op.INTERSECT.nativeInt && mLayers.size() > 0) { + for (Layer layer : mLayers) { + layer.clip(shape); + } + + Shape currentClip = getClip(); + return currentClip != null && currentClip.getBounds().isEmpty() == false; + } + + Area area = null; + + if (regionOp == Region.Op.REPLACE.nativeInt) { + area = new Area(shape); + } else { + area = Region_Delegate.combineShapes(getClip(), shape, regionOp); + } + + assert area != null; + + if (mLayers.size() > 0) { + if (area != null) { + for (Layer layer : mLayers) { + layer.setClip(area); + } + } + + Shape currentClip = getClip(); + return currentClip != null && currentClip.getBounds().isEmpty() == false; + } else { + if (area != null) { + mClip = area; + } else { + mClip = new Area(); + } + + return mClip.getBounds().isEmpty() == false; + } + } + + public boolean clipRect(float left, float top, float right, float bottom, int regionOp) { + return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp); + } + + /** + * Returns the current clip, or null if none have been setup. + */ + public Shape getClip() { + if (mLayers.size() > 0) { + // they all have the same clip + return mLayers.get(0).getGraphics().getClip(); + } else { + return mClip; + } + } + + private GcSnapshot doRestoreTo(int size, int saveCount) { + if (size <= saveCount) { + return this; + } + + // restore the current one first. + GcSnapshot previous = doRestore(); + + if (size == saveCount + 1) { // this was the only one that needed restore. + return previous; + } else { + return previous.doRestoreTo(size - 1, saveCount); + } + } + + /** + * Executes the Drawable's draw method, with a null paint delegate. + * <p/> + * Note that the method can be called several times if there are more than one active layer. + * @param drawable + */ + public void draw(Drawable drawable) { + draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/); + } + + /** + * Executes the Drawable's draw method. + * <p/> + * Note that the method can be called several times if there are more than one active layer. + * @param drawable + * @param paint + * @param compositeOnly whether the paint is used for composite only. This is typically + * the case for bitmaps. + * @param forceSrcMode if true, this overrides the composite to be SRC + */ + public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly, + boolean forceSrcMode) { + // the current snapshot may not have a mLocalLayer (ie it was created on save() instead + // of saveLayer(), but that doesn't mean there's no layer. + // mLayers however saves all the information we need (flags). + if (mLayers.size() == 1) { + // no layer, only base layer. easy case. + drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceSrcMode); + } else { + // draw in all the layers until the layer save flags tells us to stop (ie drawing + // in that layer is limited to the layer itself. + int flags; + int i = mLayers.size() - 1; + + do { + Layer layer = mLayers.get(i); + + drawInLayer(layer, drawable, paint, compositeOnly, forceSrcMode); + + // then go to previous layer, only if there are any left, and its flags + // doesn't restrict drawing to the layer itself. + i--; + flags = layer.getFlags(); + } while (i >= 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0); + } + } + + private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint, + boolean compositeOnly, boolean forceSrcMode) { + Graphics2D originalGraphics = layer.getGraphics(); + // get a Graphics2D object configured with the drawing parameters. + Graphics2D configuredGraphics2D = + paint != null ? + createCustomGraphics(originalGraphics, paint, compositeOnly, forceSrcMode) : + (Graphics2D) originalGraphics.create(); + + try { + drawable.draw(configuredGraphics2D, paint); + layer.change(); + } finally { + // dispose Graphics2D object + configuredGraphics2D.dispose(); + } + } + + private GcSnapshot doRestore() { + if (mPrevious != null) { + if (mLocalLayer != null) { + // prepare to blit the layers in which we have draw, in the layer beneath + // them, starting with the top one (which is the current local layer). + int i = mLayers.size() - 1; + int flags; + do { + Layer dstLayer = mLayers.get(i - 1); + + restoreLayer(dstLayer); + + flags = dstLayer.getFlags(); + i--; + } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0); + } + + // if this snapshot does not save everything, then set the previous snapshot + // to this snapshot content + + // didn't save the matrix? set the current matrix on the previous snapshot + if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) { + AffineTransform mtx = getTransform(); + for (Layer layer : mPrevious.mLayers) { + layer.getGraphics().setTransform(mtx); + } + } + + // didn't save the clip? set the current clip on the previous snapshot + if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) { + Shape clip = getClip(); + for (Layer layer : mPrevious.mLayers) { + layer.setClip(clip); + } + } + } + + for (Layer layer : mLayers) { + layer.getGraphics().dispose(); + } + + return mPrevious; + } + + private void restoreLayer(Layer dstLayer) { + + Graphics2D baseGfx = dstLayer.getImage().createGraphics(); + + // if the layer contains an original copy this means the flags + // didn't restrict drawing to the local layer and we need to make sure the + // layer bounds in the layer beneath didn't receive any drawing. + // so we use the originalCopy to erase the new drawings in there. + BufferedImage originalCopy = dstLayer.getOriginalCopy(); + if (originalCopy != null) { + Graphics2D g = (Graphics2D) baseGfx.create(); + g.setComposite(AlphaComposite.Src); + + g.drawImage(originalCopy, + mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, + 0, 0, mLayerBounds.width(), mLayerBounds.height(), + null); + g.dispose(); + } + + // now draw put the content of the local layer onto the layer, + // using the paint information + Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint, + true /*alphaOnly*/, false /*forceSrcMode*/); + + g.drawImage(mLocalLayer.getImage(), + mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, + mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, + null); + g.dispose(); + + baseGfx.dispose(); + } + + /** + * Creates a new {@link Graphics2D} based on the {@link Paint} parameters. + * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. + */ + private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint, + boolean compositeOnly, boolean forceSrcMode) { + // make new one graphics + Graphics2D g = (Graphics2D) original.create(); + + // configure it + + if (paint.isAntiAliased()) { + g.setRenderingHint( + RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint( + RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + } + + boolean customShader = false; + + // get the shader first, as it'll replace the color if it can be used it. + if (compositeOnly == false) { + Shader_Delegate shaderDelegate = paint.getShader(); + if (shaderDelegate != null) { + if (shaderDelegate.isSupported()) { + java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint(); + assert shaderPaint != null; + if (shaderPaint != null) { + g.setPaint(shaderPaint); + customShader = true; + } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER, + shaderDelegate.getSupportMessage(), + null /*throwable*/, null /*data*/); + } + } + + // if no shader, use the paint color + if (customShader == false) { + g.setColor(new Color(paint.getColor(), true /*hasAlpha*/)); + } + + // set the stroke + g.setStroke(paint.getJavaStroke()); + } + + // the alpha for the composite. Always opaque if the normal paint color is used since + // it contains the alpha + int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF; + + if (forceSrcMode) { + g.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC, (float) alpha / 255.f)); + } else { + boolean customXfermode = false; + Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); + if (xfermodeDelegate != null) { + if (xfermodeDelegate.isSupported()) { + Composite composite = xfermodeDelegate.getComposite(alpha); + assert composite != null; + if (composite != null) { + g.setComposite(composite); + customXfermode = true; + } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE, + xfermodeDelegate.getSupportMessage(), + null /*throwable*/, null /*data*/); + } + } + + // if there was no custom xfermode, but we have alpha (due to a shader and a non + // opaque alpha channel in the paint color), then we create an AlphaComposite anyway + // that will handle the alpha. + if (customXfermode == false && alpha != 0xFF) { + g.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, (float) alpha / 255.f)); + } + } + + return g; + } + + private void mapRect(AffineTransform matrix, RectF dst, RectF src) { + // 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. + matrix.transform(corners, 0, corners, 0, 4); + + // 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])); + } + +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java new file mode 100644 index 0000000..35e5987 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java @@ -0,0 +1,48 @@ +/* + * 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.impl; + +import com.android.ide.common.rendering.api.IAnimationListener; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.Result.Status; + +import android.animation.Animator; + +public class PlayAnimationThread extends AnimationThread { + + private final Animator mAnimator; + + public PlayAnimationThread(Animator animator, RenderSessionImpl scene, String animName, + IAnimationListener listener) { + super(scene, animName, listener); + mAnimator = animator; + } + + @Override + public Result preAnimation() { + // start the animation. This will send a message to the handler right away, so + // the queue is filled when this method returns. + mAnimator.start(); + + return Status.SUCCESS.createResult(); + } + + @Override + public void postAnimation() { + // nothing to be done. + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java new file mode 100644 index 0000000..8e80c21 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -0,0 +1,285 @@ +/* + * 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.impl; + +import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT; +import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderParams; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.resources.ResourceType; + +import android.util.DisplayMetrics; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Base class for rendering action. + * + * It provides life-cycle methods to init and stop the rendering. + * The most important methods are: + * {@link #init(long)} and {@link #acquire(long)} to start a rendering and {@link #release()} + * after the rendering. + * + * + * @param <T> the {@link RenderParams} implementation + * + */ +public abstract class RenderAction<T extends RenderParams> extends FrameworkResourceIdProvider { + + /** + * The current context being rendered. This is set through {@link #acquire(long)} and + * {@link #init(long)}, and unset in {@link #release()}. + */ + private static BridgeContext sCurrentContext = null; + + private final T mParams; + + private BridgeContext mContext; + + /** + * Creates a renderAction. + * <p> + * This <b>must</b> be followed by a call to {@link RenderAction#init()}, which act as a + * call to {@link RenderAction#acquire(long)} + * + * @param params the RenderParams. This must be a copy that the action can keep + * + */ + protected RenderAction(T params) { + mParams = params; + } + + /** + * Initializes and acquires the scene, creating various Android objects such as context, + * inflater, and parser. + * + * @param timeout the time to wait if another rendering is happening. + * + * @return whether the scene was prepared + * + * @see #acquire(long) + * @see #release() + */ + public Result init(long timeout) { + // acquire the lock. if the result is null, lock was just acquired, otherwise, return + // the result. + Result result = acquireLock(timeout); + if (result != null) { + return result; + } + + // setup the display Metrics. + DisplayMetrics metrics = new DisplayMetrics(); + metrics.densityDpi = mParams.getDensity().getDpiValue(); + metrics.density = metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT; + metrics.scaledDensity = metrics.density; + metrics.widthPixels = mParams.getScreenWidth(); + metrics.heightPixels = mParams.getScreenHeight(); + metrics.xdpi = mParams.getXdpi(); + metrics.ydpi = mParams.getYdpi(); + + RenderResources resources = mParams.getResources(); + + // build the context + mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources, + mParams.getProjectCallback(), mParams.getTargetSdkVersion()); + + setUp(); + + return SUCCESS.createResult(); + } + + /** + * Prepares the scene for action. + * <p> + * This call is blocking if another rendering/inflating is currently happening, and will return + * whether the preparation worked. + * + * The preparation can fail if another rendering took too long and the timeout was elapsed. + * + * More than one call to this from the same thread will have no effect and will return + * {@link Result#SUCCESS}. + * + * After scene actions have taken place, only one call to {@link #release()} must be + * done. + * + * @param timeout the time to wait if another rendering is happening. + * + * @return whether the scene was prepared + * + * @see #release() + * + * @throws IllegalStateException if {@link #init(long)} was never called. + */ + public Result acquire(long timeout) { + if (mContext == null) { + throw new IllegalStateException("After scene creation, #init() must be called"); + } + + // acquire the lock. if the result is null, lock was just acquired, otherwise, return + // the result. + Result result = acquireLock(timeout); + if (result != null) { + return result; + } + + setUp(); + + return SUCCESS.createResult(); + } + + /** + * Acquire the lock so that the scene can be acted upon. + * <p> + * This returns null if the lock was just acquired, otherwise it returns + * {@link Result#SUCCESS} if the lock already belonged to that thread, or another + * instance (see {@link Result#getStatus()}) if an error occurred. + * + * @param timeout the time to wait if another rendering is happening. + * @return null if the lock was just acquire or another result depending on the state. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene. + */ + private Result acquireLock(long timeout) { + ReentrantLock lock = Bridge.getLock(); + if (lock.isHeldByCurrentThread() == false) { + try { + boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS); + + if (acquired == false) { + return ERROR_TIMEOUT.createResult(); + } + } catch (InterruptedException e) { + return ERROR_LOCK_INTERRUPTED.createResult(); + } + } else { + // This thread holds the lock already. Checks that this wasn't for a different context. + // If this is called by init, mContext will be null and so should sCurrentContext + // anyway + if (mContext != sCurrentContext) { + throw new IllegalStateException("Acquiring different scenes from same thread without releases"); + } + return SUCCESS.createResult(); + } + + return null; + } + + /** + * Cleans up the scene after an action. + */ + public void release() { + ReentrantLock lock = Bridge.getLock(); + + // with the use of finally blocks, it is possible to find ourself calling this + // without a successful call to prepareScene. This test makes sure that unlock() will + // not throw IllegalMonitorStateException. + if (lock.isHeldByCurrentThread()) { + tearDown(); + lock.unlock(); + } + } + + /** + * Sets up the session for rendering. + * <p/> + * The counterpart is {@link #tearDown()}. + */ + private void setUp() { + // make sure the Resources object references the context (and other objects) for this + // scene + mContext.initResources(); + sCurrentContext = mContext; + + LayoutLog currentLog = mParams.getLog(); + Bridge.setLog(currentLog); + mContext.getRenderResources().setFrameworkResourceIdProvider(this); + mContext.getRenderResources().setLogger(currentLog); + } + + /** + * Tear down the session after rendering. + * <p/> + * The counterpart is {@link #setUp()}. + */ + private void tearDown() { + // Make sure to remove static references, otherwise we could not unload the lib + mContext.disposeResources(); + sCurrentContext = null; + + Bridge.setLog(null); + mContext.getRenderResources().setFrameworkResourceIdProvider(null); + mContext.getRenderResources().setLogger(null); + } + + public static BridgeContext getCurrentContext() { + return sCurrentContext; + } + + protected T getParams() { + return mParams; + } + + protected BridgeContext getContext() { + return mContext; + } + + /** + * Returns the log associated with the session. + * @return the log or null if there are none. + */ + public LayoutLog getLog() { + if (mParams != null) { + return mParams.getLog(); + } + + return null; + } + + /** + * Checks that the lock is owned by the current thread and that the current context is the one + * from this scene. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + */ + protected void checkLock() { + ReentrantLock lock = Bridge.getLock(); + if (lock.isHeldByCurrentThread() == false) { + throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); + } + if (sCurrentContext != mContext) { + throw new IllegalStateException("Thread acquired a scene but is rendering a different one"); + } + } + + // --- FrameworkResourceIdProvider methods + + @Override + public Integer getId(ResourceType resType, String resName) { + return Bridge.getResourceId(resType, resName); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java new file mode 100644 index 0000000..953d8cf --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2011 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.impl; + +import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; + +import com.android.ide.common.rendering.api.DrawableParams; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.Result.Status; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeWindow; +import com.android.layoutlib.bridge.android.BridgeWindowSession; +import com.android.resources.ResourceType; + +import android.graphics.Bitmap; +import android.graphics.Bitmap_Delegate; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.view.View; +import android.view.View.AttachInfo; +import android.view.View.MeasureSpec; +import android.widget.FrameLayout; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; + +/** + * Action to render a given Drawable provided through {@link DrawableParams#getDrawable()}. + * + * The class only provides a simple {@link #render()} method, but the full life-cycle of the + * action must be respected. + * + * @see RenderAction + * + */ +public class RenderDrawable extends RenderAction<DrawableParams> { + + public RenderDrawable(DrawableParams params) { + super(new DrawableParams(params)); + } + + public Result render() { + checkLock(); + try { + // get the drawable resource value + DrawableParams params = getParams(); + ResourceValue drawableResource = params.getDrawable(); + + // resolve it + BridgeContext context = getContext(); + drawableResource = context.getRenderResources().resolveResValue(drawableResource); + + if (drawableResource == null || + drawableResource.getResourceType() != ResourceType.DRAWABLE) { + return Status.ERROR_NOT_A_DRAWABLE.createResult(); + } + + // create a simple FrameLayout + FrameLayout content = new FrameLayout(context); + + // get the actual Drawable object to draw + Drawable d = ResourceHelper.getDrawable(drawableResource, context); + content.setBackgroundDrawable(d); + + // set the AttachInfo on the root view. + AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(), + new Handler(), null); + info.mHasWindowFocus = true; + info.mWindowVisibility = View.VISIBLE; + info.mInTouchMode = false; // this is so that we can display selections. + info.mHardwareAccelerated = false; + content.dispatchAttachedToWindow(info, 0); + + + // measure + int w = params.getScreenWidth(); + int h = params.getScreenHeight(); + int w_spec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY); + int h_spec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY); + content.measure(w_spec, h_spec); + + // now do the layout. + content.layout(0, 0, w, h); + + // preDraw setup + content.mAttachInfo.mTreeObserver.dispatchOnPreDraw(); + + // draw into a new image + BufferedImage image = getImage(w, h); + + // create an Android bitmap around the BufferedImage + Bitmap bitmap = Bitmap_Delegate.createBitmap(image, + true /*isMutable*/, params.getDensity()); + + // create a Canvas around the Android bitmap + Canvas canvas = new Canvas(bitmap); + canvas.setDensity(params.getDensity().getDpiValue()); + + // and draw + content.draw(canvas); + + return Status.SUCCESS.createResult(image); + } catch (IOException e) { + return ERROR_UNKNOWN.createResult(e.getMessage(), e); + } + } + + protected BufferedImage getImage(int w, int h) { + BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D gc = image.createGraphics(); + gc.setComposite(AlphaComposite.Src); + + gc.setColor(new Color(0x00000000, true)); + gc.fillRect(0, 0, w, h); + + // done + gc.dispose(); + + return image; + } + +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java new file mode 100644 index 0000000..2fd58e4 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -0,0 +1,1223 @@ +/* + * 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.impl; + +import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN; +import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; + +import com.android.ide.common.rendering.api.IAnimationListener; +import com.android.ide.common.rendering.api.ILayoutPullParser; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.RenderParams; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.Result.Status; +import com.android.ide.common.rendering.api.SessionParams.RenderingMode; +import com.android.internal.util.XmlUtils; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeInflater; +import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; +import com.android.layoutlib.bridge.android.BridgeWindow; +import com.android.layoutlib.bridge.android.BridgeWindowSession; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.bars.FakeActionBar; +import com.android.layoutlib.bridge.bars.PhoneSystemBar; +import com.android.layoutlib.bridge.bars.TabletSystemBar; +import com.android.layoutlib.bridge.bars.TitleBar; +import com.android.resources.ResourceType; +import com.android.resources.ScreenSize; +import com.android.util.Pair; + +import org.xmlpull.v1.XmlPullParserException; + +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.animation.LayoutTransition; +import android.animation.LayoutTransition.TransitionListener; +import android.app.Fragment_Delegate; +import android.graphics.Bitmap; +import android.graphics.Bitmap_Delegate; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.AttachInfo; +import android.view.View.MeasureSpec; +import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.QuickContactBadge; +import android.widget.TabHost; +import android.widget.TabWidget; +import android.widget.TabHost.TabSpec; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Class implementing the render session. + * + * A session is a stateful representation of a layout file. It is initialized with data coming + * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then + * be done on the layout. + * + */ +public class RenderSessionImpl extends RenderAction<SessionParams> { + + private static final int DEFAULT_TITLE_BAR_HEIGHT = 25; + private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; + + // scene state + private RenderSession mScene; + private BridgeXmlBlockParser mBlockParser; + private BridgeInflater mInflater; + private ResourceValue mWindowBackground; + private ViewGroup mViewRoot; + private FrameLayout mContentRoot; + private Canvas mCanvas; + private int mMeasuredScreenWidth = -1; + private int mMeasuredScreenHeight = -1; + private boolean mIsAlphaChannelImage; + private boolean mWindowIsFloating; + + private int mStatusBarSize; + private int mSystemBarSize; + private int mTitleBarSize; + private int mActionBarSize; + + + // information being returned through the API + private BufferedImage mImage; + private List<ViewInfo> mViewInfoList; + + private static final class PostInflateException extends Exception { + private static final long serialVersionUID = 1L; + + public PostInflateException(String message) { + super(message); + } + } + + /** + * Creates a layout scene with all the information coming from the layout bridge API. + * <p> + * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init()}, which act as a + * call to {@link RenderSessionImpl#acquire(long)} + * + * @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams) + */ + public RenderSessionImpl(SessionParams params) { + super(new SessionParams(params)); + } + + /** + * Initializes and acquires the scene, creating various Android objects such as context, + * inflater, and parser. + * + * @param timeout the time to wait if another rendering is happening. + * + * @return whether the scene was prepared + * + * @see #acquire(long) + * @see #release() + */ + @Override + public Result init(long timeout) { + Result result = super.init(timeout); + if (result.isSuccess() == false) { + return result; + } + + SessionParams params = getParams(); + BridgeContext context = getContext(); + + RenderResources resources = getParams().getResources(); + DisplayMetrics metrics = getContext().getMetrics(); + + // use default of true in case it's not found to use alpha by default + mIsAlphaChannelImage = getBooleanThemeValue(resources, + "windowIsFloating", true /*defaultValue*/); + + mWindowIsFloating = getBooleanThemeValue(resources, "windowIsFloating", + true /*defaultValue*/); + + findBackground(resources); + findStatusBar(resources, metrics); + findActionBar(resources, metrics); + findSystemBar(resources, metrics); + + // build the inflater and parser. + mInflater = new BridgeInflater(context, params.getProjectCallback()); + context.setBridgeInflater(mInflater); + mInflater.setFactory2(context); + + mBlockParser = new BridgeXmlBlockParser( + params.getLayoutDescription(), context, false /* platformResourceFlag */); + + return SUCCESS.createResult(); + } + + /** + * Inflates the layout. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #init(long)} was not called. + */ + public Result inflate() { + checkLock(); + + try { + + SessionParams params = getParams(); + BridgeContext context = getContext(); + + // the view group that receives the window background. + ViewGroup backgroundView = null; + + if (mWindowIsFloating || params.isForceNoDecor()) { + backgroundView = mViewRoot = mContentRoot = new FrameLayout(context); + } else { + /* + * we're creating the following layout + * + +-------------------------------------------------+ + | System bar (only in phone UI) | + +-------------------------------------------------+ + | (Layout with background drawable) | + | +---------------------------------------------+ | + | | Title/Action bar (optional) | | + | +---------------------------------------------+ | + | | Content, vertical extending | | + | | | | + | +---------------------------------------------+ | + +-------------------------------------------------+ + | System bar (only in tablet UI) | + +-------------------------------------------------+ + + */ + + LinearLayout topLayout = new LinearLayout(context); + mViewRoot = topLayout; + topLayout.setOrientation(LinearLayout.VERTICAL); + + if (mStatusBarSize > 0) { + // system bar + try { + PhoneSystemBar systemBar = new PhoneSystemBar(context, + params.getDensity()); + systemBar.setLayoutParams( + new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, mStatusBarSize)); + topLayout.addView(systemBar); + } catch (XmlPullParserException e) { + + } + } + + LinearLayout backgroundLayout = new LinearLayout(context); + backgroundView = backgroundLayout; + backgroundLayout.setOrientation(LinearLayout.VERTICAL); + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + layoutParams.weight = 1; + backgroundLayout.setLayoutParams(layoutParams); + topLayout.addView(backgroundLayout); + + + // if the theme says no title/action bar, then the size will be 0 + if (mActionBarSize > 0) { + try { + FakeActionBar actionBar = new FakeActionBar(context, + params.getDensity(), + params.getAppLabel(), params.getAppIcon()); + actionBar.setLayoutParams( + new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, mActionBarSize)); + backgroundLayout.addView(actionBar); + } catch (XmlPullParserException e) { + + } + } else if (mTitleBarSize > 0) { + try { + TitleBar titleBar = new TitleBar(context, + params.getDensity(), params.getAppLabel()); + titleBar.setLayoutParams( + new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, mTitleBarSize)); + backgroundLayout.addView(titleBar); + } catch (XmlPullParserException e) { + + } + } + + + // content frame + mContentRoot = new FrameLayout(context); + layoutParams = new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + layoutParams.weight = 1; + mContentRoot.setLayoutParams(layoutParams); + backgroundLayout.addView(mContentRoot); + + if (mSystemBarSize > 0) { + // system bar + try { + TabletSystemBar systemBar = new TabletSystemBar(context, + params.getDensity()); + systemBar.setLayoutParams( + new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, mSystemBarSize)); + topLayout.addView(systemBar); + } catch (XmlPullParserException e) { + + } + } + } + + + // Sets the project callback (custom view loader) to the fragment delegate so that + // it can instantiate the custom Fragment. + Fragment_Delegate.setProjectCallback(params.getProjectCallback()); + + View view = mInflater.inflate(mBlockParser, mContentRoot); + + Fragment_Delegate.setProjectCallback(null); + + // set the AttachInfo on the root view. + AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(), + new Handler(), null); + info.mHasWindowFocus = true; + info.mWindowVisibility = View.VISIBLE; + info.mInTouchMode = false; // this is so that we can display selections. + info.mHardwareAccelerated = false; + mViewRoot.dispatchAttachedToWindow(info, 0); + + // post-inflate process. For now this supports TabHost/TabWidget + postInflateProcess(view, params.getProjectCallback()); + + // get the background drawable + if (mWindowBackground != null && backgroundView != null) { + Drawable d = ResourceHelper.getDrawable(mWindowBackground, context); + backgroundView.setBackgroundDrawable(d); + } + + return SUCCESS.createResult(); + } catch (PostInflateException e) { + return ERROR_INFLATION.createResult(e.getMessage(), e); + } catch (Throwable e) { + // get the real cause of the exception. + Throwable t = e; + while (t.getCause() != null) { + t = t.getCause(); + } + + return ERROR_INFLATION.createResult(t.getMessage(), t); + } + } + + /** + * Renders the scene. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @param freshRender whether the render is a new one and should erase the existing bitmap (in + * the case where bitmaps are reused). This is typically needed when not playing + * animations.) + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see RenderParams#getRenderingMode() + * @see RenderSession#render(long) + */ + public Result render(boolean freshRender) { + checkLock(); + + SessionParams params = getParams(); + + try { + if (mViewRoot == null) { + return ERROR_NOT_INFLATED.createResult(); + } + // measure the views + int w_spec, h_spec; + + RenderingMode renderingMode = params.getRenderingMode(); + + // only do the screen measure when needed. + boolean newRenderSize = false; + if (mMeasuredScreenWidth == -1) { + newRenderSize = true; + mMeasuredScreenWidth = params.getScreenWidth(); + mMeasuredScreenHeight = params.getScreenHeight(); + + if (renderingMode != RenderingMode.NORMAL) { + // measure the full size needed by the layout. + w_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenWidth, + renderingMode.isHorizExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY); + h_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenHeight, + renderingMode.isVertExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY); + mViewRoot.measure(w_spec, h_spec); + + if (renderingMode.isHorizExpand()) { + int neededWidth = mViewRoot.getChildAt(0).getMeasuredWidth(); + if (neededWidth > mMeasuredScreenWidth) { + mMeasuredScreenWidth = neededWidth; + } + } + + if (renderingMode.isVertExpand()) { + int neededHeight = mViewRoot.getChildAt(0).getMeasuredHeight(); + if (neededHeight > mMeasuredScreenHeight) { + mMeasuredScreenHeight = neededHeight; + } + } + } + } + + // remeasure with the size we need + // This must always be done before the call to layout + w_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenWidth, MeasureSpec.EXACTLY); + h_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenHeight, MeasureSpec.EXACTLY); + mViewRoot.measure(w_spec, h_spec); + + // now do the layout. + mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); + + if (params.isLayoutOnly()) { + // delete the canvas and image to reset them on the next full rendering + mImage = null; + mCanvas = null; + } else { + mViewRoot.mAttachInfo.mTreeObserver.dispatchOnPreDraw(); + + // draw the views + // create the BufferedImage into which the layout will be rendered. + boolean newImage = false; + if (newRenderSize || mCanvas == null) { + if (params.getImageFactory() != null) { + mImage = params.getImageFactory().getImage( + mMeasuredScreenWidth, + mMeasuredScreenHeight); + } else { + mImage = new BufferedImage( + mMeasuredScreenWidth, + mMeasuredScreenHeight, + BufferedImage.TYPE_INT_ARGB); + newImage = true; + } + + if (params.isBgColorOverridden()) { + // since we override the content, it's the same as if it was a new image. + newImage = true; + Graphics2D gc = mImage.createGraphics(); + gc.setColor(new Color(params.getOverrideBgColor(), true)); + gc.setComposite(AlphaComposite.Src); + gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); + gc.dispose(); + } + + // create an Android bitmap around the BufferedImage + Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage, + true /*isMutable*/, params.getDensity()); + + // create a Canvas around the Android bitmap + mCanvas = new Canvas(bitmap); + mCanvas.setDensity(params.getDensity().getDpiValue()); + } + + if (freshRender && newImage == false) { + Graphics2D gc = mImage.createGraphics(); + gc.setComposite(AlphaComposite.Src); + + gc.setColor(new Color(0x00000000, true)); + gc.fillRect(0, 0, + mMeasuredScreenWidth, mMeasuredScreenHeight); + + // done + gc.dispose(); + } + + mViewRoot.draw(mCanvas); + } + + mViewInfoList = startVisitingViews(mViewRoot, 0); + + // success! + return SUCCESS.createResult(); + } catch (Throwable e) { + // get the real cause of the exception. + Throwable t = e; + while (t.getCause() != null) { + t = t.getCause(); + } + + return ERROR_UNKNOWN.createResult(t.getMessage(), t); + } + } + + /** + * Animate an object + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see RenderSession#animate(Object, String, boolean, IAnimationListener) + */ + public Result animate(Object targetObject, String animationName, + boolean isFrameworkAnimation, IAnimationListener listener) { + checkLock(); + + BridgeContext context = getContext(); + + // find the animation file. + ResourceValue animationResource = null; + int animationId = 0; + if (isFrameworkAnimation) { + animationResource = context.getRenderResources().getFrameworkResource( + ResourceType.ANIMATOR, animationName); + if (animationResource != null) { + animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName); + } + } else { + animationResource = context.getRenderResources().getProjectResource( + ResourceType.ANIMATOR, animationName); + if (animationResource != null) { + animationId = context.getProjectCallback().getResourceId( + ResourceType.ANIMATOR, animationName); + } + } + + if (animationResource != null) { + try { + Animator anim = AnimatorInflater.loadAnimator(context, animationId); + if (anim != null) { + anim.setTarget(targetObject); + + new PlayAnimationThread(anim, this, animationName, listener).start(); + + return SUCCESS.createResult(); + } + } catch (Exception e) { + // get the real cause of the exception. + Throwable t = e; + while (t.getCause() != null) { + t = t.getCause(); + } + + return ERROR_UNKNOWN.createResult(t.getMessage(), t); + } + } + + return ERROR_ANIM_NOT_FOUND.createResult(); + } + + /** + * Insert a new child into an existing parent. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener) + */ + public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml, + final int index, IAnimationListener listener) { + checkLock(); + + BridgeContext context = getContext(); + + // create a block parser for the XML + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( + childXml, context, false /* platformResourceFlag */); + + // inflate the child without adding it to the root since we want to control where it'll + // get added. We do pass the parentView however to ensure that the layoutParams will + // be created correctly. + final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/); + blockParser.ensurePopped(); + + invalidateRenderingSize(); + + if (listener != null) { + new AnimationThread(this, "insertChild", listener) { + + @Override + public Result preAnimation() { + parentView.setLayoutTransition(new LayoutTransition()); + return addView(parentView, child, index); + } + + @Override + public void postAnimation() { + parentView.setLayoutTransition(null); + } + }.start(); + + // always return success since the real status will come through the listener. + return SUCCESS.createResult(child); + } + + // add it to the parentView in the correct location + Result result = addView(parentView, child, index); + if (result.isSuccess() == false) { + return result; + } + + result = render(false /*freshRender*/); + if (result.isSuccess()) { + result = result.getCopyWithData(child); + } + + return result; + } + + /** + * Adds a given view to a given parent at a given index. + * + * @param parent the parent to receive the view + * @param view the view to add to the parent + * @param index the index where to do the add. + * + * @return a Result with {@link Status#SUCCESS} or + * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support + * adding views. + */ + private Result addView(ViewGroup parent, View view, int index) { + try { + parent.addView(view, index); + return SUCCESS.createResult(); + } catch (UnsupportedOperationException e) { + // looks like this is a view class that doesn't support children manipulation! + return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); + } + } + + /** + * Moves a view to a new parent at a given location + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see RenderSession#moveChild(Object, Object, int, Map, IAnimationListener) + */ + public Result moveChild(final ViewGroup newParentView, final View childView, final int index, + Map<String, String> layoutParamsMap, final IAnimationListener listener) { + checkLock(); + + invalidateRenderingSize(); + + LayoutParams layoutParams = null; + if (layoutParamsMap != null) { + // need to create a new LayoutParams object for the new parent. + layoutParams = newParentView.generateLayoutParams( + new BridgeLayoutParamsMapAttributes(layoutParamsMap)); + } + + // get the current parent of the view that needs to be moved. + final ViewGroup previousParent = (ViewGroup) childView.getParent(); + + if (listener != null) { + final LayoutParams params = layoutParams; + + // there is no support for animating views across layouts, so in case the new and old + // parent views are different we fake the animation through a no animation thread. + if (previousParent != newParentView) { + new Thread("not animated moveChild") { + @Override + public void run() { + Result result = moveView(previousParent, newParentView, childView, index, + params); + if (result.isSuccess() == false) { + listener.done(result); + } + + // ready to do the work, acquire the scene. + result = acquire(250); + if (result.isSuccess() == false) { + listener.done(result); + return; + } + + try { + result = render(false /*freshRender*/); + if (result.isSuccess()) { + listener.onNewFrame(RenderSessionImpl.this.getSession()); + } + } finally { + release(); + } + + listener.done(result); + } + }.start(); + } else { + new AnimationThread(this, "moveChild", listener) { + + @Override + public Result preAnimation() { + // set up the transition for the parent. + LayoutTransition transition = new LayoutTransition(); + previousParent.setLayoutTransition(transition); + + // tweak the animation durations and start delays (to match the duration of + // animation playing just before). + // Note: Cannot user Animation.setDuration() directly. Have to set it + // on the LayoutTransition. + transition.setDuration(LayoutTransition.DISAPPEARING, 100); + // CHANGE_DISAPPEARING plays after DISAPPEARING + transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100); + + transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100); + + transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100); + // CHANGE_APPEARING plays after CHANGE_APPEARING + transition.setStartDelay(LayoutTransition.APPEARING, 100); + + transition.setDuration(LayoutTransition.APPEARING, 100); + + return moveView(previousParent, newParentView, childView, index, params); + } + + @Override + public void postAnimation() { + previousParent.setLayoutTransition(null); + newParentView.setLayoutTransition(null); + } + }.start(); + } + + // always return success since the real status will come through the listener. + return SUCCESS.createResult(layoutParams); + } + + Result result = moveView(previousParent, newParentView, childView, index, layoutParams); + if (result.isSuccess() == false) { + return result; + } + + result = render(false /*freshRender*/); + if (layoutParams != null && result.isSuccess()) { + result = result.getCopyWithData(layoutParams); + } + + return result; + } + + /** + * Moves a View from its current parent to a new given parent at a new given location, with + * an optional new {@link LayoutParams} instance + * + * @param previousParent the previous parent, still owning the child at the time of the call. + * @param newParent the new parent + * @param movedView the view to move + * @param index the new location in the new parent + * @param params an option (can be null) {@link LayoutParams} instance. + * + * @return a Result with {@link Status#SUCCESS} or + * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support + * adding views. + */ + private Result moveView(ViewGroup previousParent, final ViewGroup newParent, + final View movedView, final int index, final LayoutParams params) { + try { + // check if there is a transition on the previousParent. + LayoutTransition previousTransition = previousParent.getLayoutTransition(); + if (previousTransition != null) { + // in this case there is an animation. This means we have to wait for the child's + // parent reference to be null'ed out so that we can add it to the new parent. + // It is technically removed right before the DISAPPEARING animation is done (if + // the animation of this type is not null, otherwise it's after which is impossible + // to handle). + // Because there is no move animation, if the new parent is the same as the old + // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before + // adding the child or the child will appear in its new location before the + // other children have made room for it. + + // add a listener to the transition to be notified of the actual removal. + previousTransition.addTransitionListener(new TransitionListener() { + private int mChangeDisappearingCount = 0; + + public void startTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType) { + if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) { + mChangeDisappearingCount++; + } + } + + public void endTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType) { + if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) { + mChangeDisappearingCount--; + } + + if (transitionType == LayoutTransition.CHANGE_DISAPPEARING && + mChangeDisappearingCount == 0) { + // add it to the parentView in the correct location + if (params != null) { + newParent.addView(movedView, index, params); + } else { + newParent.addView(movedView, index); + } + } + } + }); + + // remove the view from the current parent. + previousParent.removeView(movedView); + + // and return since adding the view to the new parent is done in the listener. + return SUCCESS.createResult(); + } else { + // standard code with no animation. pretty simple. + previousParent.removeView(movedView); + + // add it to the parentView in the correct location + if (params != null) { + newParent.addView(movedView, index, params); + } else { + newParent.addView(movedView, index); + } + + return SUCCESS.createResult(); + } + } catch (UnsupportedOperationException e) { + // looks like this is a view class that doesn't support children manipulation! + return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); + } + } + + /** + * Removes a child from its current parent. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see RenderSession#removeChild(Object, IAnimationListener) + */ + public Result removeChild(final View childView, IAnimationListener listener) { + checkLock(); + + invalidateRenderingSize(); + + final ViewGroup parent = (ViewGroup) childView.getParent(); + + if (listener != null) { + new AnimationThread(this, "moveChild", listener) { + + @Override + public Result preAnimation() { + parent.setLayoutTransition(new LayoutTransition()); + return removeView(parent, childView); + } + + @Override + public void postAnimation() { + parent.setLayoutTransition(null); + } + }.start(); + + // always return success since the real status will come through the listener. + return SUCCESS.createResult(); + } + + Result result = removeView(parent, childView); + if (result.isSuccess() == false) { + return result; + } + + return render(false /*freshRender*/); + } + + /** + * Removes a given view from its current parent. + * + * @param view the view to remove from its parent + * + * @return a Result with {@link Status#SUCCESS} or + * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support + * adding views. + */ + private Result removeView(ViewGroup parent, View view) { + try { + parent.removeView(view); + return SUCCESS.createResult(); + } catch (UnsupportedOperationException e) { + // looks like this is a view class that doesn't support children manipulation! + return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); + } + } + + + private void findBackground(RenderResources resources) { + if (getParams().isBgColorOverridden() == false) { + mWindowBackground = resources.findItemInTheme("windowBackground"); + if (mWindowBackground != null) { + mWindowBackground = resources.resolveResValue(mWindowBackground); + } + } + } + + private boolean isTabletUi() { + return getParams().getConfigScreenSize() == ScreenSize.XLARGE; + } + + private void findStatusBar(RenderResources resources, DisplayMetrics metrics) { + if (isTabletUi() == false) { + boolean windowFullscreen = getBooleanThemeValue(resources, + "windowFullscreen", false /*defaultValue*/); + + if (windowFullscreen == false && mWindowIsFloating == false) { + // default value + mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT; + + // get the real value + ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN, + "status_bar_height"); + + if (value != null) { + TypedValue typedValue = ResourceHelper.getValue(value.getValue()); + if (typedValue != null) { + // compute the pixel value based on the display metrics + mStatusBarSize = (int)typedValue.getDimension(metrics); + } + } + } + } + } + + private void findActionBar(RenderResources resources, DisplayMetrics metrics) { + if (mWindowIsFloating) { + return; + } + + boolean windowActionBar = getBooleanThemeValue(resources, + "windowActionBar", true /*defaultValue*/); + + // if there's a value and it's false (default is true) + if (windowActionBar) { + + // default size of the window title bar + mActionBarSize = DEFAULT_TITLE_BAR_HEIGHT; + + // get value from the theme. + ResourceValue value = resources.findItemInTheme("actionBarSize"); + + // resolve it + value = resources.resolveResValue(value); + + if (value != null) { + // get the numerical value, if available + TypedValue typedValue = ResourceHelper.getValue(value.getValue()); + if (typedValue != null) { + // compute the pixel value based on the display metrics + mActionBarSize = (int)typedValue.getDimension(metrics); + } + } + } else { + // action bar overrides title bar so only look for this one if action bar is hidden + boolean windowNoTitle = getBooleanThemeValue(resources, + "windowNoTitle", false /*defaultValue*/); + + if (windowNoTitle == false) { + + // default size of the window title bar + mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT; + + // get value from the theme. + ResourceValue value = resources.findItemInTheme("windowTitleSize"); + + // resolve it + value = resources.resolveResValue(value); + + if (value != null) { + // get the numerical value, if available + TypedValue typedValue = ResourceHelper.getValue(value.getValue()); + if (typedValue != null) { + // compute the pixel value based on the display metrics + mTitleBarSize = (int)typedValue.getDimension(metrics); + } + } + } + + } + } + + private void findSystemBar(RenderResources resources, DisplayMetrics metrics) { + if (isTabletUi() && mWindowIsFloating == false) { + + // default value + mSystemBarSize = 48; // ?? + + // get the real value + ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN, + "status_bar_height"); + + if (value != null) { + TypedValue typedValue = ResourceHelper.getValue(value.getValue()); + if (typedValue != null) { + // compute the pixel value based on the display metrics + mSystemBarSize = (int)typedValue.getDimension(metrics); + } + } + } + } + + private boolean getBooleanThemeValue(RenderResources resources, + String name, boolean defaultValue) { + + // get the title bar flag from the current theme. + ResourceValue value = resources.findItemInTheme(name); + + // because it may reference something else, we resolve it. + value = resources.resolveResValue(value); + + // if there's no value, return the default. + if (value == null || value.getValue() == null) { + return defaultValue; + } + + return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue); + } + + /** + * Post process on a view hierachy that was just inflated. + * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the + * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically + * based on the content of the {@link FrameLayout}. + * @param view the root view to process. + * @param projectCallback callback to the project. + */ + private void postInflateProcess(View view, IProjectCallback projectCallback) + throws PostInflateException { + if (view instanceof TabHost) { + setupTabHost((TabHost)view, projectCallback); + } else if (view instanceof QuickContactBadge) { + QuickContactBadge badge = (QuickContactBadge) view; + badge.setImageToDefault(); + } else if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup)view; + final int count = group.getChildCount(); + for (int c = 0 ; c < count ; c++) { + View child = group.getChildAt(c); + postInflateProcess(child, projectCallback); + } + } + } + + /** + * Sets up a {@link TabHost} object. + * @param tabHost the TabHost to setup. + * @param projectCallback The project callback object to access the project R class. + * @throws PostInflateException + */ + private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback) + throws PostInflateException { + // look for the TabWidget, and the FrameLayout. They have their own specific names + View v = tabHost.findViewById(android.R.id.tabs); + + if (v == null) { + throw new PostInflateException( + "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); + } + + if ((v instanceof TabWidget) == false) { + throw new PostInflateException(String.format( + "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + + "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); + } + + v = tabHost.findViewById(android.R.id.tabcontent); + + if (v == null) { + // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) + throw new PostInflateException( + "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); + } + + if ((v instanceof FrameLayout) == false) { + throw new PostInflateException(String.format( + "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + + "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); + } + + FrameLayout content = (FrameLayout)v; + + // now process the content of the framelayout and dynamically create tabs for it. + final int count = content.getChildCount(); + + // this must be called before addTab() so that the TabHost searches its TabWidget + // and FrameLayout. + tabHost.setup(); + + if (count == 0) { + // Create a dummy child to get a single tab + TabSpec spec = tabHost.newTabSpec("tag").setIndicator("Tab Label", + tabHost.getResources().getDrawable(android.R.drawable.ic_menu_info_details)) + .setContent(new TabHost.TabContentFactory() { + public View createTabContent(String tag) { + return new LinearLayout(getContext()); + } + }); + tabHost.addTab(spec); + return; + } else { + // for each child of the framelayout, add a new TabSpec + for (int i = 0 ; i < count ; i++) { + View child = content.getChildAt(i); + String tabSpec = String.format("tab_spec%d", i+1); + int id = child.getId(); + Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id); + String name; + if (resource != null) { + name = resource.getSecond(); + } else { + name = String.format("Tab %d", i+1); // default name if id is unresolved. + } + tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id)); + } + } + } + + private List<ViewInfo> startVisitingViews(View view, int offset) { + if (view == null) { + return null; + } + + // adjust the offset to this view. + offset += view.getTop(); + + if (view == mContentRoot) { + return visitAllChildren(mContentRoot, offset); + } + + // otherwise, look for mContentRoot in the children + if (view instanceof ViewGroup) { + ViewGroup group = ((ViewGroup) view); + + for (int i = 0; i < group.getChildCount(); i++) { + List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset); + if (list != null) { + return list; + } + } + } + + return null; + } + + /** + * Visits a View and its children and generate a {@link ViewInfo} containing the + * bounds of all the views. + * @param view the root View + * @param offset an offset for the view bounds. + */ + private ViewInfo visit(View view, int offset) { + if (view == null) { + return null; + } + + ViewInfo result = new ViewInfo(view.getClass().getName(), + getContext().getViewKey(view), + view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset, + view, view.getLayoutParams()); + + if (view instanceof ViewGroup) { + ViewGroup group = ((ViewGroup) view); + result.setChildren(visitAllChildren(group, 0 /*offset*/)); + } + + return result; + } + + /** + * Visits all the children of a given ViewGroup generate a list of {@link ViewInfo} + * containing the bounds of all the views. + * @param view the root View + * @param offset an offset for the view bounds. + */ + private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset) { + if (viewGroup == null) { + return null; + } + + List<ViewInfo> children = new ArrayList<ViewInfo>(); + for (int i = 0; i < viewGroup.getChildCount(); i++) { + children.add(visit(viewGroup.getChildAt(i), offset)); + } + return children; + } + + + private void invalidateRenderingSize() { + mMeasuredScreenWidth = mMeasuredScreenHeight = -1; + } + + public BufferedImage getImage() { + return mImage; + } + + public boolean isAlphaChannelImage() { + return mIsAlphaChannelImage; + } + + public List<ViewInfo> getViewInfos() { + return mViewInfoList; + } + + public Map<String, String> getDefaultProperties(Object viewObject) { + return getContext().getDefaultPropMap(viewObject); + } + + public void setScene(RenderSession session) { + mScene = session; + } + + public RenderSession getSession() { + return mScene; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index f624753..69f46e6 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -14,33 +14,45 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; - -import com.android.layoutlib.api.IDensityBasedResourceValue; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IDensityBasedResourceValue.Density; +package com.android.layoutlib.bridge.impl; + +import com.android.ide.common.rendering.api.DensityBasedResourceValue; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.ninepatch.NinePatch; +import com.android.ninepatch.NinePatchChunk; +import com.android.resources.Density; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.content.res.ColorStateList; import android.graphics.Bitmap; +import android.graphics.Bitmap_Delegate; +import android.graphics.NinePatch_Delegate; +import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; import android.util.TypedValue; import java.io.File; -import java.io.FileNotFoundException; +import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * Helper class to provide various convertion method used in handling android resources. + * Helper class to provide various conversion method used in handling android resources. */ public final class ResourceHelper { @@ -55,17 +67,21 @@ public final class ResourceHelper { * @return the color as an int * @throw NumberFormatException if the conversion failed. */ - static int getColor(String value) { + public static int getColor(String value) { if (value != null) { if (value.startsWith("#") == false) { - throw new NumberFormatException(); + throw new NumberFormatException( + String.format("Color value '%s' must start with #", value)); } value = value.substring(1); // make sure it's not longer than 32bit if (value.length() > 8) { - throw new NumberFormatException(); + throw new NumberFormatException(String.format( + "Color value '%s' is too long. Format is either" + + "#AARRGGBB, #RRGGBB, #RGB, or #ARGB", + value)); } if (value.length() == 3) { // RGB format @@ -97,48 +113,93 @@ public final class ResourceHelper { throw new NumberFormatException(); } + public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) { + String value = resValue.getValue(); + if (value != null) { + // first check if the value is a file (xml most likely) + File f = new File(value); + if (f.isFile()) { + try { + // let the framework inflate the ColorStateList from the XML file, by + // providing an XmlPullParser + KXmlParser parser = new KXmlParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new FileReader(f)); + + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( + parser, context, resValue.isFramework()); + try { + return ColorStateList.createFromXml(context.getResources(), blockParser); + } finally { + blockParser.ensurePopped(); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value, e, null /*data*/); + // we'll return null below. + } catch (Exception e) { + // this is an error and not warning since the file existence is + // checked before attempting to parse it. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed to parse file " + value, e, null /*data*/); + + return null; + } + } else { + // try to load the color state list from an int + try { + int color = ResourceHelper.getColor(value); + return ColorStateList.valueOf(color); + } catch (NumberFormatException e) { + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, + "Failed to convert " + value + " into a ColorStateList", e, + null /*data*/); + return null; + } + } + } + + return null; + } + /** * Returns a drawable from the given value. * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable, * or an hexadecimal color - * @param context - * @param isFramework indicates whether the resource is a framework resources. - * Framework resources are cached, and loaded only once. + * @param context the current context */ - public static Drawable getDrawable(IResourceValue value, BridgeContext context, boolean isFramework) { - Drawable d = null; - + public static Drawable getDrawable(ResourceValue value, BridgeContext context) { String stringValue = value.getValue(); + if (RenderResources.REFERENCE_NULL.equals(stringValue)) { + return null; + } String lowerCaseValue = stringValue.toLowerCase(); + Density density = Density.MEDIUM; + if (value instanceof DensityBasedResourceValue) { + density = + ((DensityBasedResourceValue)value).getResourceDensity(); + } + + if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) { File file = new File(stringValue); if (file.isFile()) { - NinePatch ninePatch = Bridge.getCached9Patch(stringValue, - isFramework ? null : context.getProjectKey()); - - if (ninePatch == null) { - try { - ninePatch = NinePatch.load(file.toURL(), false /* convert */); - - Bridge.setCached9Patch(stringValue, ninePatch, - isFramework ? null : context.getProjectKey()); - } catch (MalformedURLException e) { - // URL is wrong, we'll return null below - } catch (IOException e) { - // failed to read the file, we'll return null below. - } - } - - if (ninePatch != null) { - return new NinePatchDrawable(ninePatch); + try { + return getNinePatchDrawable( + new FileInputStream(file), density, value.isFramework(), + stringValue, context); + } catch (IOException e) { + // failed to read the file, we'll return null below. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed lot load " + file.getAbsolutePath(), e, null /*data*/); } } return null; } else if (lowerCaseValue.endsWith(".xml")) { - // create a blockparser for the file + // create a block parser for the file File f = new File(stringValue); if (f.isFile()) { try { @@ -147,16 +208,23 @@ public final class ResourceHelper { parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new FileReader(f)); - d = Drawable.createFromXml(context.getResources(), - new BridgeXmlBlockParser(parser, context, isFramework)); - return d; - } catch (XmlPullParserException e) { - context.getLogger().error(e); - } catch (FileNotFoundException e) { - // will not happen, since we pre-check - } catch (IOException e) { - context.getLogger().error(e); + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( + parser, context, value.isFramework()); + try { + return Drawable.createFromXml(context.getResources(), blockParser); + } finally { + blockParser.ensurePopped(); + } + } catch (Exception e) { + // this is an error and not warning since the file existence is checked before + // attempting to parse it. + Bridge.getLog().error(null, "Failed to parse file " + stringValue, + e, null /*data*/); } + } else { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("File %s does not exist (or is not a file)", stringValue), + null /*data*/); } return null; @@ -165,42 +233,20 @@ public final class ResourceHelper { if (bmpFile.isFile()) { try { Bitmap bitmap = Bridge.getCachedBitmap(stringValue, - isFramework ? null : context.getProjectKey()); + value.isFramework() ? null : context.getProjectKey()); if (bitmap == null) { - bitmap = new Bitmap(bmpFile); - try { - bitmap.setDensity(Density.MEDIUM.getValue()); - } catch (NoClassDefFoundError error) { - // look like we're running in an older version of ADT that doesn't - // include the new layoutlib_api. Let's just ignore this, the drawing - // will just be wrong. - } + bitmap = Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/, + density); Bridge.setCachedBitmap(stringValue, bitmap, - isFramework ? null : context.getProjectKey()); - } - - try { - if (value instanceof IDensityBasedResourceValue) { - Density density = ((IDensityBasedResourceValue)value).getDensity(); - if (density != Density.MEDIUM) { - // create a copy of the bitmap - bitmap = Bitmap.createBitmap(bitmap); - - // apply the density - bitmap.setDensity(density.getValue()); - } - } - } catch (NoClassDefFoundError error) { - // look like we're running in an older version of ADT that doesn't include - // the new layoutlib_api. Let's just ignore this, the drawing will just be - // wrong. + value.isFramework() ? null : context.getProjectKey()); } return new BitmapDrawable(context.getResources(), bitmap); } catch (IOException e) { // we'll return null below - // TODO: log the error. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed lot load " + bmpFile.getAbsolutePath(), e, null /*data*/); } } else { // attempt to get a color from the value @@ -209,7 +255,9 @@ public final class ResourceHelper { return new ColorDrawable(color); } catch (NumberFormatException e) { // we'll return null below. - // TODO: log the error + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, + "Failed to convert " + stringValue + " into a drawable", e, + null /*data*/); } } } @@ -217,6 +265,52 @@ public final class ResourceHelper { return null; } + private static Drawable getNinePatchDrawable(InputStream inputStream, Density density, + boolean isFramework, String cacheKey, BridgeContext context) throws IOException { + // see if we still have both the chunk and the bitmap in the caches + NinePatchChunk chunk = Bridge.getCached9Patch(cacheKey, + isFramework ? null : context.getProjectKey()); + Bitmap bitmap = Bridge.getCachedBitmap(cacheKey, + isFramework ? null : context.getProjectKey()); + + // if either chunk or bitmap is null, then we reload the 9-patch file. + if (chunk == null || bitmap == null) { + try { + NinePatch ninePatch = NinePatch.load(inputStream, true /*is9Patch*/, + false /* convert */); + if (ninePatch != null) { + if (chunk == null) { + chunk = ninePatch.getChunk(); + + Bridge.setCached9Patch(cacheKey, chunk, + isFramework ? null : context.getProjectKey()); + } + + if (bitmap == null) { + bitmap = Bitmap_Delegate.createBitmap(ninePatch.getImage(), + false /*isMutable*/, + density); + + Bridge.setCachedBitmap(cacheKey, bitmap, + isFramework ? null : context.getProjectKey()); + } + } + } catch (MalformedURLException e) { + // URL is wrong, we'll return null below + } + } + + if (chunk != null && bitmap != null) { + int[] padding = chunk.getPadding(); + Rect paddingRect = new Rect(padding[0], padding[1], padding[2], padding[3]); + + return new NinePatchDrawable(context.getResources(), bitmap, + NinePatch_Delegate.serialize(chunk), + paddingRect, null); + } + + return null; + } // ------- TypedValue stuff // This is taken from //device/libs/utils/ResourceTypes.cpp @@ -267,7 +361,7 @@ public final class ResourceHelper { */ public static boolean stringToFloat(String s, TypedValue outValue) { // remove the space before and after - s.trim(); + s = s.trim(); int len = s.length(); if (len <= 0) { @@ -379,3 +473,4 @@ public final class ResourceHelper { return false; } } + diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java new file mode 100644 index 0000000..9bd0015 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java @@ -0,0 +1,70 @@ +/* + * 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.impl; + +import java.util.ArrayList; + +/** + * Custom Stack implementation on top of an {@link ArrayList} instead of + * using {@link java.util.Stack} which is on top of a vector. + * + * @param <T> + */ +public class Stack<T> extends ArrayList<T> { + + private static final long serialVersionUID = 1L; + + public Stack() { + super(); + } + + public Stack(int size) { + super(size); + } + + /** + * Pushes the given object to the stack + * @param object the object to push + */ + public void push(T object) { + add(object); + } + + /** + * Remove the object at the top of the stack and returns it. + * @return the removed object or null if the stack was empty. + */ + public T pop() { + if (size() > 0) { + return remove(size() - 1); + } + + return null; + } + + /** + * Returns the object at the top of the stack. + * @return the object at the top or null if the stack is empty. + */ + public T peek() { + if (size() > 0) { + return get(size() - 1); + } + + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java new file mode 100644 index 0000000..82eab85 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2011 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.util; + +public class Debug { + + public final static boolean DEBUG = false; + +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java new file mode 100644 index 0000000..4d0c9ce --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2011 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.util; + + +import com.android.internal.util.ArrayUtils; + +import android.util.SparseArray; + +import java.lang.ref.WeakReference; + +/** + * This is a custom {@link SparseArray} that uses {@link WeakReference} around the objects added + * to it. When the array is compacted, not only deleted indices but also empty references + * are removed, making the array efficient at removing references that were reclaimed. + * + * The code is taken from {@link SparseArray} directly and adapted to use weak references. + * + * Because our usage means that we never actually call {@link #remove(int)} or {@link #delete(int)}, + * we must manually check if there are reclaimed references to trigger an internal compact step + * (which is normally only triggered when an item is manually removed). + * + * SparseArrays map integers to Objects. Unlike a normal array of Objects, + * there can be gaps in the indices. It is intended to be more efficient + * than using a HashMap to map Integers to Objects. + */ +@SuppressWarnings("unchecked") +public class SparseWeakArray<E> { + + private static final Object DELETED_REF = new Object(); + private static final WeakReference<?> DELETED = new WeakReference(DELETED_REF); + private boolean mGarbage = false; + + /** + * Creates a new SparseArray containing no mappings. + */ + public SparseWeakArray() { + this(10); + } + + /** + * Creates a new SparseArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. + */ + public SparseWeakArray(int initialCapacity) { + initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity); + + mKeys = new int[initialCapacity]; + mValues = new WeakReference[initialCapacity]; + mSize = 0; + } + + /** + * Gets the Object mapped from the specified key, or <code>null</code> + * if no such mapping has been made. + */ + public E get(int key) { + return get(key, null); + } + + /** + * Gets the Object mapped from the specified key, or the specified Object + * if no such mapping has been made. + */ + public E get(int key, E valueIfKeyNotFound) { + int i = binarySearch(mKeys, 0, mSize, key); + + if (i < 0 || mValues[i] == DELETED || mValues[i].get() == null) { + return valueIfKeyNotFound; + } else { + return (E) mValues[i].get(); + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + public void delete(int key) { + int i = binarySearch(mKeys, 0, mSize, key); + + if (i >= 0) { + if (mValues[i] != DELETED) { + mValues[i] = DELETED; + mGarbage = true; + } + } + } + + /** + * Alias for {@link #delete(int)}. + */ + public void remove(int key) { + delete(key); + } + + /** + * Removes the mapping at the specified index. + */ + public void removeAt(int index) { + if (mValues[index] != DELETED) { + mValues[index] = DELETED; + mGarbage = true; + } + } + + private void gc() { + int n = mSize; + int o = 0; + int[] keys = mKeys; + WeakReference<?>[] values = mValues; + + for (int i = 0; i < n; i++) { + WeakReference<?> val = values[i]; + + // Don't keep any non DELETED values, but only the one that still have a valid + // reference. + if (val != DELETED && val.get() != null) { + if (i != o) { + keys[o] = keys[i]; + values[o] = val; + } + + o++; + } + } + + mGarbage = false; + mSize = o; + + int newSize = ArrayUtils.idealIntArraySize(mSize); + if (newSize < mKeys.length) { + int[] nkeys = new int[newSize]; + WeakReference<?>[] nvalues = new WeakReference[newSize]; + + System.arraycopy(mKeys, 0, nkeys, 0, newSize); + System.arraycopy(mValues, 0, nvalues, 0, newSize); + + mKeys = nkeys; + mValues = nvalues; + } + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(int key, E value) { + int i = binarySearch(mKeys, 0, mSize, key); + + if (i >= 0) { + mValues[i] = new WeakReference(value); + } else { + i = ~i; + + if (i < mSize && (mValues[i] == DELETED || mValues[i].get() == null)) { + mKeys[i] = key; + mValues[i] = new WeakReference(value); + return; + } + + if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) { + gc(); + + // Search again because indices may have changed. + i = ~binarySearch(mKeys, 0, mSize, key); + } + + if (mSize >= mKeys.length) { + int n = ArrayUtils.idealIntArraySize(mSize + 1); + + int[] nkeys = new int[n]; + WeakReference<?>[] nvalues = new WeakReference[n]; + + // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); + System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); + System.arraycopy(mValues, 0, nvalues, 0, mValues.length); + + mKeys = nkeys; + mValues = nvalues; + } + + if (mSize - i != 0) { + // Log.e("SparseArray", "move " + (mSize - i)); + System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); + System.arraycopy(mValues, i, mValues, i + 1, mSize - i); + } + + mKeys[i] = key; + mValues[i] = new WeakReference(value); + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this SparseArray + * currently stores. + */ + public int size() { + if (mGarbage) { + gc(); + } + + return mSize; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the key from the <code>index</code>th key-value mapping that this + * SparseArray stores. + */ + public int keyAt(int index) { + if (mGarbage) { + gc(); + } + + return mKeys[index]; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the value from the <code>index</code>th key-value mapping that this + * SparseArray stores. + */ + public E valueAt(int index) { + if (mGarbage) { + gc(); + } + + return (E) mValues[index].get(); + } + + /** + * Given an index in the range <code>0...size()-1</code>, sets a new + * value for the <code>index</code>th key-value mapping that this + * SparseArray stores. + */ + public void setValueAt(int index, E value) { + if (mGarbage) { + gc(); + } + + mValues[index] = new WeakReference(value); + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(int key) { + if (mGarbage) { + gc(); + } + + return binarySearch(mKeys, 0, mSize, key); + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + * Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + */ + public int indexOfValue(E value) { + if (mGarbage) { + gc(); + } + + for (int i = 0; i < mSize; i++) + if (mValues[i].get() == value) + return i; + + return -1; + } + + /** + * Removes all key-value mappings from this SparseArray. + */ + public void clear() { + int n = mSize; + WeakReference<?>[] values = mValues; + + for (int i = 0; i < n; i++) { + values[i] = null; + } + + mSize = 0; + mGarbage = false; + } + + /** + * Puts a key/value pair into the array, optimizing for the case where + * the key is greater than all existing keys in the array. + */ + public void append(int key, E value) { + if (mSize != 0 && key <= mKeys[mSize - 1]) { + put(key, value); + return; + } + + if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) { + gc(); + } + + int pos = mSize; + if (pos >= mKeys.length) { + int n = ArrayUtils.idealIntArraySize(pos + 1); + + int[] nkeys = new int[n]; + WeakReference<?>[] nvalues = new WeakReference[n]; + + // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); + System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); + System.arraycopy(mValues, 0, nvalues, 0, mValues.length); + + mKeys = nkeys; + mValues = nvalues; + } + + mKeys[pos] = key; + mValues[pos] = new WeakReference(value); + mSize = pos + 1; + } + + private boolean hasReclaimedRefs() { + for (int i = 0 ; i < mSize ; i++) { + if (mValues[i].get() == null) { // DELETED.get() never returns null. + return true; + } + } + + return false; + } + + private static int binarySearch(int[] a, int start, int len, int key) { + int high = start + len, low = start - 1, guess; + + while (high - low > 1) { + guess = (high + low) / 2; + + if (a[guess] < key) + low = guess; + else + high = guess; + } + + if (high == start + len) + return ~(start + len); + else if (a[high] == key) + return high; + else + return ~high; + } + + private int[] mKeys; + private WeakReference<?>[] mValues; + private int mSize; +} diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java new file mode 100644 index 0000000..e6dc646 --- /dev/null +++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2011 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 libcore.icu; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.util.Locale; + +/** + * Delegate implementing the native methods of libcore.icu.ICU + * + * Through the layoutlib_create tool, the original native methods of ICU have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class ICU_Delegate { + + // --- Java delegates + + @LayoutlibDelegate + /*package*/ static String toLowerCase(String s, String localeName) { + return s.toLowerCase(); + } + + @LayoutlibDelegate + /*package*/ static String toUpperCase(String s, String localeName) { + return s.toUpperCase(); + } + + // --- Native methods accessing ICU's database. + + @LayoutlibDelegate + /*package*/ static String[] getAvailableBreakIteratorLocalesNative() { + return new String[0]; + } + + @LayoutlibDelegate + /*package*/ static String[] getAvailableCalendarLocalesNative() { + return new String[0]; + } + + @LayoutlibDelegate + /*package*/ static String[] getAvailableCollatorLocalesNative() { + return new String[0]; + } + + @LayoutlibDelegate + /*package*/ static String[] getAvailableDateFormatLocalesNative() { + return new String[0]; + } + + @LayoutlibDelegate + /*package*/ static String[] getAvailableLocalesNative() { + return new String[0]; + } + + @LayoutlibDelegate + /*package*/ static String[] getAvailableNumberFormatLocalesNative() { + return new String[0]; + } + + @LayoutlibDelegate + /*package*/ static String getCurrencyCodeNative(String locale) { + return ""; + } + + @LayoutlibDelegate + /*package*/ static int getCurrencyFractionDigitsNative(String currencyCode) { + return 0; + } + + @LayoutlibDelegate + /*package*/ static String getCurrencySymbolNative(String locale, String currencyCode) { + return ""; + } + + @LayoutlibDelegate + /*package*/ static String getDisplayCountryNative(String countryCode, String locale) { + return ""; + } + + @LayoutlibDelegate + /*package*/ static String getDisplayLanguageNative(String languageCode, String locale) { + return ""; + } + + @LayoutlibDelegate + /*package*/ static String getDisplayVariantNative(String variantCode, String locale) { + return ""; + } + + @LayoutlibDelegate + /*package*/ static String getISO3CountryNative(String locale) { + return ""; + } + + @LayoutlibDelegate + /*package*/ static String getISO3LanguageNative(String locale) { + return ""; + } + + @LayoutlibDelegate + /*package*/ static String[] getISOLanguagesNative() { + return Locale.getISOLanguages(); + } + + @LayoutlibDelegate + /*package*/ static String[] getISOCountriesNative() { + return Locale.getISOCountries(); + } + + @LayoutlibDelegate + /*package*/ static boolean initLocaleDataImpl(String locale, LocaleData result) { + + // Used by Calendar. + result.firstDayOfWeek = Integer.valueOf(1); + result.minimalDaysInFirstWeek = Integer.valueOf(1); + + // Used by DateFormatSymbols. + result.amPm = new String[] { "AM", "PM" }; + result.eras = new String[] { "BC", "AD" }; + + result.longMonthNames = new String[] { "January", "February", "March", "April", "May", + "June", "July", "August", "September", "October", "November", "December" }; + result.shortMonthNames = new String[] { "Jan", "Feb", "Mar", "Apr", "May", + "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + result.longStandAloneMonthNames = result.longMonthNames; + result.shortStandAloneMonthNames = result.shortMonthNames; + + result.longWeekdayNames = new String[] { + "Monday" ,"Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; + result.shortWeekdayNames = new String[] { + "Mon" ,"Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; + result.longStandAloneWeekdayNames = result.longWeekdayNames; + result.shortStandAloneWeekdayNames = result.shortWeekdayNames; + + result.fullTimeFormat = ""; + result.longTimeFormat = ""; + result.mediumTimeFormat = ""; + result.shortTimeFormat = ""; + + result.fullDateFormat = ""; + result.longDateFormat = ""; + result.mediumDateFormat = ""; + result.shortDateFormat = ""; + + // Used by DecimalFormatSymbols. + result.zeroDigit = '0'; + result.digit = '0'; + result.decimalSeparator = '.'; + result.groupingSeparator = ','; + result.patternSeparator = ' '; + result.percent = '%'; + result.perMill = '\u2030'; + result.monetarySeparator = ' '; + result.minusSign = '-'; + result.exponentSeparator = "e"; + result.infinity = "\u221E"; + result.NaN = "NaN"; + // Also used by Currency. + result.currencySymbol = "$"; + result.internationalCurrencySymbol = "USD"; + + // Used by DecimalFormat and NumberFormat. + result.numberPattern = "%f"; + result.integerPattern = "%d"; + result.currencyPattern = "%s"; + result.percentPattern = "%f"; + + return true; + } +} diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java index 6e14e82..ba3c51a 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java @@ -17,8 +17,6 @@ package com.android.layoutlib.bridge; import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics._Original_Paint; import android.text.TextPaint; import junit.framework.TestCase; @@ -58,14 +56,6 @@ public class AndroidGraphicsTests extends TestCase { } } - public void testPaint() { - _Original_Paint o = new _Original_Paint(); - assertNotNull(o); - - Paint p = new Paint(); - assertNotNull(p); - } - public void textTextPaint() { TextPaint p = new TextPaint(); assertNotNull(p); diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java index 23351ab..a3219e7 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java @@ -48,5 +48,4 @@ public class NinePatchTest extends TestCase { assertEquals(36, mPatch.getWidth()); assertEquals(25, mPatch.getHeight()); } - } diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java deleted file mode 100644 index e0dc55f..0000000 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2009 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 java.lang.reflect.Method; - -import junit.framework.TestCase; - -public class TestClassReplacement extends TestCase { - - public void testClassReplacements() { - // 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" - }; - final int count = classes.length; - for (int i = 0 ; i < count ; i += 2) { - loadAndCompareClasses(classes[i], classes[i+1]); - } - } - - private void loadAndCompareClasses(String newClassName, String oldClassName) { - // load the classes - try { - Class<?> newClass = TestClassReplacement.class.getClassLoader().loadClass(newClassName); - Class<?> oldClass = TestClassReplacement.class.getClassLoader().loadClass(oldClassName); - - compare(newClass, oldClass); - } catch (ClassNotFoundException e) { - fail("Failed to load class: " + e.getMessage()); - } - } - - private void compare(Class<?> newClass, Class<?> oldClass) { - // first compare the methods. - Method[] newClassMethods = newClass.getDeclaredMethods(); - Method[] oldClassMethods = oldClass.getDeclaredMethods(); - - for (Method oldMethod : oldClassMethods) { - // we ignore anything that starts with native - if (oldMethod.getName().startsWith("native")) { - continue; - } - boolean found = false; - for (Method newMethod : newClassMethods) { - if (compareMethods(newClass, newMethod, oldClass, oldMethod)) { - found = true; - break; - } - } - - if (found == false) { - fail(String.format("Unable to find %1$s", oldMethod.toGenericString())); - } - } - - // TODO: check (somehow?) that the methods that were removed from the original class - // have been put back in the new class! - // For this we need the original unmodified class (ie renamed, but w/o the methods removed) - } - - private boolean compareMethods(Class<?> newClass, Method newMethod, - Class<?> oldClass, Method oldMethod) { - // first check the name of the method - if (newMethod.getName().equals(oldMethod.getName()) == false) { - return false; - } - - // check the return value - Class<?> oldReturnType = oldMethod.getReturnType(); - // if it's the old class, or if it's a inner class of the oldclass, we need to change this. - oldReturnType = adapt(oldReturnType, newClass, oldClass); - - // compare the return types - Class<?> newReturnType = newMethod.getReturnType(); - if (newReturnType.equals(oldReturnType) == false) { - return false; - } - - // now check the parameters type. - Class<?>[] oldParameters = oldMethod.getParameterTypes(); - Class<?>[] newParemeters = newMethod.getParameterTypes(); - if (oldParameters.length != newParemeters.length) { - return false; - } - - for (int i = 0 ; i < oldParameters.length ; i++) { - if (newParemeters[i].equals(adapt(oldParameters[i], newClass, oldClass)) == false) { - return false; - } - } - - return true; - } - - /** - * Adapts a class to deal with renamed classes. - * <p/>For instance if old class is <code>android.graphics._Original_Paint</code> and the - * new class is <code>android.graphics.Paint</code> and the class to adapt is - * <code>android.graphics._Original_Paint$Cap</code>, then the method will return a - * {@link Class} object representing <code>android.graphics.Paint$Cap</code>. - * <p/> - * This method will also ensure that all renamed classes contains all the proper inner classes - * that they should be declaring. - * @param theClass the class to adapt - * @param newClass the new class object - * @param oldClass the old class object - * @return the adapted class. - * @throws ClassNotFoundException - */ - private Class<?> adapt(Class<?> theClass, Class<?> newClass, Class<?> oldClass) { - // only look for a new class if it's not primitive as Class.forName() would fail otherwise. - if (theClass.isPrimitive() == false) { - String n = theClass.getName().replace(oldClass.getName(), newClass.getName()); - try { - return Class.forName(n); - } catch (ClassNotFoundException e) { - fail("Missing class: " + n); - } - } - - return theClass; - } -} diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestDelegates.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestDelegates.java new file mode 100644 index 0000000..d3218db --- /dev/null +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestDelegates.java @@ -0,0 +1,197 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +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 TestDelegates 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"); + } + } + + public void testMethodDelegates() { + final String[] methods = CreateInfo.DELEGATE_METHODS; + final int count = methods.length; + for (int i = 0 ; i < count ; i++) { + String methodName = methods[i]; + + // extract the class name + String className = methodName.substring(0, methodName.indexOf('#')); + String targetClassName = className.replace('$', '_') + "_Delegate"; + + loadAndCompareClasses(className, targetClassName); + } + } + + private void loadAndCompareClasses(String originalClassName, String delegateClassName) { + // load the classes + try { + ClassLoader classLoader = TestDelegates.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 { + List<Method> checkedDelegateMethods = new ArrayList<Method>(); + + // loop on the methods of the original class, and for the ones that are annotated + // with @LayoutlibDelegate, look for a matching method in the delegate class. + // The annotation is automatically added by layoutlib_create when it replace a method + // by a call to a delegate + Method[] originalMethods = originalClass.getDeclaredMethods(); + for (Method originalMethod : originalMethods) { + // look for methods that are delegated: 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; + } + + // if the original class is an inner class that's not static, then + // we add this on the enclosing class at the beginning + if (originalClass.getEnclosingClass() != null && + (originalClass.getModifiers() & Modifier.STATIC) == 0) { + Class<?>[] newParameters = new Class<?>[parameters.length + 1]; + newParameters[0] = originalClass.getEnclosingClass(); + System.arraycopy(parameters, 0, newParameters, 1, parameters.length); + parameters = newParameters; + } + + try { + // try to load the method with the given parameter types. + Method delegateMethod = delegateClass.getDeclaredMethod(originalMethod.getName(), + parameters); + + // check that the method has the annotation + assertNotNull( + String.format( + "Delegate method %1$s for class %2$s does not have the @LayoutlibDelegate annotation", + delegateMethod.getName(), + originalClass.getName()), + delegateMethod.getAnnotation(LayoutlibDelegate.class)); + + // check that the method is static + assertTrue( + String.format( + "Delegate method %1$s for class %2$s is not static", + delegateMethod.getName(), + originalClass.getName()), + (delegateMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC); + + // add the method as checked. + checkedDelegateMethods.add(delegateMethod); + } catch (NoSuchMethodException e) { + String name = getMethodName(originalMethod, parameters); + fail(String.format("Missing %1$s.%2$s", delegateClass.getName(), name)); + } + } + + // look for dead (delegate) code. + // This looks for all methods in the delegate class, and if they have the + // @LayoutlibDelegate annotation, make sure they have been previously found as a + // match for a method in the original class. + // If not, this means the method is a delegate for a method that either doesn't exist + // anymore or is not delegated anymore. + Method[] delegateMethods = delegateClass.getDeclaredMethods(); + for (Method delegateMethod : delegateMethods) { + // look for methods that are delegates: they have the LayoutlibDelegate annotation + if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) { + continue; + } + + assertTrue( + String.format( + "Delegate method %1$s.%2$s is not used anymore and must be removed", + delegateClass.getName(), + getMethodName(delegateMethod)), + checkedDelegateMethods.contains(delegateMethod)); + } + + } + + private String getMethodName(Method method) { + return getMethodName(method, method.getParameterTypes()); + } + + private String getMethodName(Method method, Class<?>[] parameters) { + // compute a full class name that's long but not too long. + StringBuilder sb = new StringBuilder(method.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(")"); + + return sb.toString(); + } +} diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java index db1262f..3252fb4 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeXmlBlockParserTest.java +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.android; + +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import org.kxml2.io.KXmlParser; import org.w3c.dom.Node; diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt index 09b392b..65a64cd 100644 --- a/tools/layoutlib/create/README.txt +++ b/tools/layoutlib/create/README.txt @@ -4,68 +4,213 @@ - Description - --------------- -makeLayoutLib generates a library used by the Eclipse graphical layout editor +Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor to perform layout. - - Usage - --------- - ./makeLayoutLib path/to/android.jar destination.jar + ./layoutlib_create path/to/android.jar destination.jar + + +- Design Overview - +------------------- + +Layoutlib_create uses the "android.jar" containing all the Java code used by Android +as generated by the Android build, right before the classes are converted to a DEX format. + +The Android JAR can't be used directly in Eclipse: +- it contains references to native code (which we want to avoid in Eclipse), +- some classes need to be overridden, for example all the drawing code that is + replaced by Java 2D calls in Eclipse. +- some of the classes that need to be changed are final and/or we need access + to their private internal state. + +Consequently this tool: +- parses the input JAR, +- modifies some of the classes directly using some bytecode manipulation, +- filters some packages and removes some that we don't want to end in the output JAR, +- injects some new classes, +- and generates a modified JAR file that is suitable for the Android plugin + for Eclipse to perform rendering. + +The ASM library is used to do the bytecode modification using its visitor pattern API. + +The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the +configuration is done in the main() method and the CreateInfo structure is expected to +change with the Android platform as new classes are added, changed or removed. + +The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the +platform, that provides all the necessary missing implementation for rendering graphics +in Eclipse. - Implementation Notes - ------------------------ -The goal of makeLayoutLib is to list all the classes from the input jar and create a new -jar that only keeps certain classes and create stubs for all their dependencies. +The tool works in two phases: +- first analyze the input jar (AsmAnalyzer class) +- then generate the output jar (AsmGenerator class), + + +- Analyzer +---------- + +The goal of the analyzer is to create a graph of all the classes from the input JAR +with their dependencies and then only keep the ones we want. + +To do that, the analyzer is created with a list of base classes to keep -- everything +that derives from these is kept. Currently the one such class is android.view.View: +since we want to render layouts, anything that is sort of the view needs to be kept. + +The analyzer is also given a list of class names to keep in the output. +This is done using shell-like glob patterns that filter on the fully-qualified +class names, for example "android.*.R**" ("*" does not matches dots whilst "**" does, +and "." and "$" are interpreted as-is). +In practice we almost but not quite request the inclusion of full packages. + +With this information, the analyzer parses the input zip to find all the classes. +All classes deriving from the requested bases classes are kept. +All classes which name matched the glob pattern are kept. +The analysis then finds all the dependencies of the classes that are to be kept +using an ASM visitor on the class, the field types, the method types and annotations types. +Classes that belong to the current JRE are excluded. + +The output of the analyzer is a set of ASM ClassReader instances which are then +fed to the generator. + + +- Generator +----------- + +The generator is constructed from a CreateInfo struct that acts as a config file +and lists: +- the classes to inject in the output JAR -- these classes are directly implemented + in layoutlib_create and will be used to interface with the renderer in Eclipse. +- specific methods to override (see method stubs details below). +- specific methods to remove based on their return type. +- specific classes to rename. + +Each of these are specific strategies we use to be able to modify the Android code +to fit within the Eclipse renderer. These strategies are explained beow. + +The core method of the generator is transform(): it takes an input ASM ClassReader +and modifies it to produce a byte array suitable for the final JAR file. -First the input jar is parsed to find all the classes defined. +The first step of the transformation is changing the name of the class in case +we requested the class to be renamed. This uses the RenameClassAdapter to also rename +all inner classes and references in methods and types. Note that other classes are +not transformed and keep referencing the original name. -In the Main(), the following list of classes are hardcoded (TODO config file later): -- keep all classes that derive from android.view.View. -- keep all classes in the android.view and android.widget packages (sub-packages excluded). -- keep specific classes such as android.policy.PhoneLayoutInflater. +The TransformClassAdapter is then used to process the potentially renamed class. +All protected or private classes are market as public. +All classes are made non-final. +Interfaces are left as-is. -For each class to keep, their dependencies are examined using BCEL. -A dependency is defined as a class needed to instantiate the given class that should be kept, -directly or indirectly. So a dependency is a class that is used by the input class, that is -defined in the input jar and that is not part of the current JRE. +If a method has a return type that must be erased, the whole method is skipped. +Methods are also changed from protected/private to public. +The code of the methods is then kept as-is, except for native methods which are +replaced by a stub. Methods that are to be overridden are also replaced by a stub. -Dependencies are computed recursively. +Finally fields are also visited and changed from protected/private to public. -Once all dependencies are found, the final jar can be created. -There are three kind of classes to write: -- classes that are to be kept as-is. They are just dumped in the new jar unchanged. -- classes that are to be kept yet contain native methods or fields. -- classes that are just dependencies. We don't want to expose their implementation in the final - jar. -The implementation of native methods and all methods of mock classes is replaced by a stub -that throws UnsupportedOperationException. +- Method stubs +-------------- -Incidentally, the access level of native and mock classes needs to be changed in order for -native methods to be later overridden. Methods that are "final private native" must become -non-final, non-native and at most protected. Package-default access is changed to public. -Classes that are final are made non-final. Abstract methods are left untouched. +As indicated above, all native and overridden methods are replaced by a stub. +We don't have the code to replace with in layoutlib_create. +Instead the StubMethodAdapter replaces the code of the method by a call to +OverrideMethod.invokeX(). When using the final JAR, the bridge can register +listeners from these overridden method calls based on the method signatures. +The listeners are currently pretty basic: we only pass the signature of the +method being called, its caller object and a flag indicating whether the +method was native. We do not currently provide the parameters. The listener +can however specify the return value of the overridden method. +An extension being worked on is to actually replace these listeners by +direct calls to a delegate class, complete with parameters. ----- -20080617 Replace Class -Some classes are basically wrappers over native objects. -Subclassing doesn't work as most methods are either static or we don't -control object creation. In this scenario the idea is to be able to -replace classes in the final jar. +- Strategies +------------ -Example: android.graphics.Paint would get renamed to OriginalPaint -in the generated jar. Then in the bridge we'll introduce a replacement -Paint class that derives from OriginalPaint. +We currently have 4 strategies to deal with overriding the rendering code +and make it run in Eclipse. Most of these strategies are implemented hand-in-hand +by the bridge (which runs in Eclipse) and the generator. + + +1- Class Injection + +This is the easiest: we currently inject 4 classes, namely: +- OverrideMethod and its associated MethodListener and MethodAdapter are used + to intercept calls to some specific methods that are stubbed out and change + their return value. +- CreateInfo class, which configured the generator. Not used yet, but could + in theory help us track what the generator changed. + + +2- Overriding methods + +As explained earlier, the creator doesn't have any replacement code for +methods to override. Instead it removes the original code and replaces it +by a call to a specific OveriddeMethod.invokeX(). The bridge then registers +a listener on the method signature and can provide an implementation. + + +3- Renaming classes + +This simply changes the name of a class in its definition, as well as all its +references in internal inner classes and methods. +Calls from other classes are not modified -- they keep referencing the original +class name. This allows the bridge to literally replace an implementation. + +An example will make this easier: android.graphics.Paint is the main drawing +class that we need to replace. To do so, the generator renames Paint to _original_Paint. +Later the bridge provides its own replacement version of Paint which will be used +by the rest of the Android stack. The replacement version of Paint can still use +(either by inheritance or delegation) all the original non-native code of _original_Paint +if it so desires. + +Some of the Android classes are basically wrappers over native objects and since +we don't have the native code in Eclipse, we need to provide a full alternate +implementation. Sub-classing doesn't work as some native methods are static and +we don't control object creation. This won't rename/replace the inner static methods of a given class. +4- Method erasure based on return type + +This is mostly an implementation detail of the bridge: in the Paint class +mentioned above, some inner static classes are used to pass around +attributes (e.g. FontMetrics, or the Style enum) and all the original implementation +is native. + +In this case we have a strategy that tells the generator that anything returning, for +example, the inner class Paint$Style in the Paint class should be discarded and the +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/annotations/Nullable.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java new file mode 100755 index 0000000..0689c92 --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java @@ -0,0 +1,35 @@ +/* + * 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 parameter or field can be null. + * <p/> + * When decorating a method call parameter, this denotes the parameter can + * legitimately be null and the method will gracefully deal with it. Typically used + * on optional parameters. + * <p/> + * When decorating a method, this denotes the method might legitimately return null. + * <p/> + * This is a marker annotation and it has no specific attributes. + */ +@Retention(RetentionPolicy.SOURCE) +public @interface Nullable { +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java new file mode 100755 index 0000000..e4e016b --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java @@ -0,0 +1,50 @@ +/* + * 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 that the class, method or field has its visibility relaxed so + * that unit tests can access it. + * <p/> + * The <code>visibility</code> argument can be used to specific what the original + * visibility should have been if it had not been made public or package-private for testing. + * The default is to consider the element private. + */ +@Retention(RetentionPolicy.SOURCE) +public @interface VisibleForTesting { + /** + * Intended visibility if the element had not been made public or package-private for + * testing. + */ + enum Visibility { + /** The element should be considered protected. */ + PROTECTED, + /** The element should be considered package-private. */ + PACKAGE, + /** The element should be considered private. */ + PRIVATE + } + + /** + * Intended visibility if the element had not been made public or package-private for testing. + * If not specified, one should assume the element originally intended to be private. + */ + Visibility visibility() default Visibility.PRIVATE; +} 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/ClassHasNativeVisitor.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java index 5424efa..722dce2 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java @@ -16,6 +16,9 @@ package com.android.tools.layoutlib.create; +import com.android.tools.layoutlib.annotations.VisibleForTesting; +import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility; + import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassVisitor; @@ -27,13 +30,18 @@ import org.objectweb.asm.Opcodes; * Indicates if a class contains any native methods. */ public class ClassHasNativeVisitor implements ClassVisitor { - + private boolean mHasNativeMethods = false; - + public boolean hasNativeMethods() { return mHasNativeMethods; } + @VisibleForTesting(visibility=Visibility.PRIVATE) + protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) { + mHasNativeMethods = hasNativeMethods; + } + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { // pass @@ -65,7 +73,9 @@ public class ClassHasNativeVisitor implements ClassVisitor { public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - mHasNativeMethods |= ((access & Opcodes.ACC_NATIVE) != 0); + if ((access & Opcodes.ACC_NATIVE) != 0) { + setHasNativeMethods(true, name); + } return null; } 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..eff6bbc 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,51 +16,160 @@ 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[] { + "android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;", + "android.content.res.Resources$Theme#obtainStyledAttributes", + "android.content.res.Resources$Theme#resolveAttribute", + "android.graphics.BitmapFactory#finishDecode", + "android.os.Handler#sendMessageAtTime", + "android.os.Build#getString", + "android.view.LayoutInflater#rInflate", + "android.view.View#isInEditMode", + "com.android.internal.util.XmlUtils#convertValueToInt", + // TODO: comment out once DelegateClass is working + }; + + /** + * The list of classes on which to delegate all native methods. + */ + private final static String[] DELEGATE_CLASS_NATIVES = new String[] { + "android.animation.PropertyValuesHolder", + "android.graphics.AvoidXfermode", + "android.graphics.Bitmap", + "android.graphics.BitmapFactory", + "android.graphics.BitmapShader", + "android.graphics.BlurMaskFilter", + "android.graphics.Canvas", + "android.graphics.ColorFilter", + "android.graphics.ColorMatrixColorFilter", + "android.graphics.ComposePathEffect", + "android.graphics.ComposeShader", + "android.graphics.CornerPathEffect", + "android.graphics.DashPathEffect", + "android.graphics.DiscretePathEffect", + "android.graphics.DrawFilter", + "android.graphics.EmbossMaskFilter", + "android.graphics.LayerRasterizer", + "android.graphics.LightingColorFilter", + "android.graphics.LinearGradient", + "android.graphics.MaskFilter", + "android.graphics.Matrix", + "android.graphics.NinePatch", + "android.graphics.Paint", + "android.graphics.PaintFlagsDrawFilter", + "android.graphics.Path", + "android.graphics.PathDashPathEffect", + "android.graphics.PathEffect", + "android.graphics.PixelXorXfermode", + "android.graphics.PorterDuffColorFilter", + "android.graphics.PorterDuffXfermode", + "android.graphics.RadialGradient", + "android.graphics.Rasterizer", + "android.graphics.Region", + "android.graphics.Shader", + "android.graphics.SumPathEffect", + "android.graphics.SweepGradient", + "android.graphics.Typeface", + "android.graphics.Xfermode", + "android.os.SystemClock", + "android.util.FloatMath", + "libcore.icu.ICU", + }; + + /** * 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[] { + }; /** * 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", - "android.graphics.BitmapShader", "android.graphics._Original_BitmapShader", - "android.graphics.Canvas", "android.graphics._Original_Canvas", - "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", - "android.graphics.RadialGradient", "android.graphics._Original_RadialGradient", - "android.graphics.Shader", "android.graphics._Original_Shader", - "android.graphics.SweepGradient", "android.graphics._Original_SweepGradient", - "android.graphics.Typeface", "android.graphics._Original_Typeface", "android.os.ServiceManager", "android.os._Original_ServiceManager", - "android.util.FloatMath", "android.util._Original_FloatMath", "android.view.SurfaceView", "android.view._Original_SurfaceView", "android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager", + "android.webkit.WebView", "android.webkit._Original_WebView", }; /** @@ -69,15 +178,8 @@ 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 - "android.graphics.Paint$Style", - "android.graphics.Paint$Join", - "android.graphics.Paint$Cap", - "android.graphics.Paint$FontMetrics", - "android.graphics.Paint$FontMetricsInt", null }; // separator, for next class/methods list. } 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..8d7f016 --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java @@ -0,0 +1,358 @@ +/* + * 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; + +import java.util.ArrayList; + +/** + * 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 non-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); + * + * If the method is non-static and the class name is an inner class (e.g. has $ in its + * last segment), we want to push the 'this' of the outer class first: + * OuterClass_InnerClass_Delegate.method_1( + * OuterClass.this, + * OuterClass$InnerClass.this, + * a, b, c); + * + * Only one level of inner class is supported right now, for simplicity and because + * we don't need more. + * + * 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 name 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; + } + + ArrayList<Type> paramTypes = new ArrayList<Type>(); + String delegateClassName = mClassName + DELEGATE_SUFFIX; + boolean pushedArg0 = false; + int maxStack = 0; + + // For an instance method (e.g. non-static), push the 'this' preceded + // by the 'this' of any outer class, if any. + if (!mIsStatic) { + // Check if the last segment of the class name has inner an class. + // Right now we only support one level of inner classes. + int slash = mClassName.lastIndexOf('/'); + int dol = mClassName.lastIndexOf('$'); + if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) { + String outerClass = mClassName.substring(0, dol); + Type outerType = Type.getObjectType(outerClass); + + // Change a delegate class name to "com/foo/Outer_Inner_Delegate" + delegateClassName = delegateClassName.replace('$', '_'); + + // The first-level inner class has a package-protected member called 'this$0' + // that points to the outer class. + + // Push this.getField("this$0") on the call stack. + mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this + mParentVisitor.visitFieldInsn(Opcodes.GETFIELD, + mClassName, // class where the field is defined + "this$0", // field name + outerType.getDescriptor()); // type of the field + maxStack++; + paramTypes.add(outerType); + } + + // Push "this" for the instance method, which is always ALOAD 0 + mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); + maxStack++; + pushedArg0 = true; + paramTypes.add(Type.getObjectType(mClassName)); + } + + // Push all other arguments. Start at arg 1 if we already pushed 'this' above. + Type[] argTypes = Type.getArgumentTypes(mDesc); + int maxLocals = pushedArg0 ? 1 : 0; + for (Type t : argTypes) { + int size = t.getSize(); + mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals); + maxLocals += size; + maxStack += size; + paramTypes.add(t); + } + + // Construct the descriptor of the delegate based on the parameters + // we pushed on the call stack. The return type remains unchanged. + String desc = Type.getMethodDescriptor( + Type.getReturnType(mDesc), + paramTypes.toArray(new Type[paramTypes.size()])); + + // Invoke the static delegate + mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, + delegateClassName, + mMethodName, + desc); + + Type returnType = Type.getReturnType(mDesc); + mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); + + mParentVisitor.visitMaxs(maxStack, maxLocals); + 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..ce48069 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,15 +63,13 @@ 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 + new String[] { // derived from + "android.view.View", + "android.app.Fragment" + }, new String[] { // include classes "android.*", // for android.R "android.util.*", 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 new file mode 100644 index 0000000..0135c40 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java @@ -0,0 +1,102 @@ +/* + * 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.*; + +import org.junit.Test; +import org.objectweb.asm.ClassReader; + +import java.io.IOException; +import java.util.ArrayList; + + +/** + * Tests {@link ClassHasNativeVisitor}. + */ +public class ClassHasNativeVisitorTest { + + @Test + public void testHasNative() throws IOException { + MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor(); + 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()); + assertTrue(cv.hasNativeMethods()); + } + + @Test + public void testHasNoNative() throws IOException { + MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor(); + 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. + */ + private static class MockClassHasNativeVisitor extends ClassHasNativeVisitor { + private ArrayList<String> mMethodsFound = new ArrayList<String>(); + + public String[] getMethodsFound() { + return mMethodsFound.toArray(new String[mMethodsFound.size()]); + } + + @Override + protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) { + if (hasNativeMethods) { + mMethodsFound.add(methodName); + } + super.setHasNativeMethods(hasNativeMethods, methodName); + } + } + + /** + * Dummy test class with a native method. + */ + public static class ClassWithNative { + public ClassWithNative() { + } + + public void callTheNativeMethod() { + native_method(); + } + + private native void native_method(); + } + + /** + * Dummy test class with no native method. + */ + public static class ClassWithoutNative { + public ClassWithoutNative() { + } + + public void someMethod() { + } + } +} 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..e8b3ea8 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -0,0 +1,409 @@ +/* + * 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 com.android.tools.layoutlib.create.dataclass.ClassWithNative; +import com.android.tools.layoutlib.create.dataclass.OuterClass; +import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass; + +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class DelegateClassAdapterTest { + + private MockLog mLog; + + private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getCanonicalName(); + private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName(); + private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" + + InnerClass.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 Throwable { + // 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(NATIVE_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 = NATIVE_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cv = new DelegateClassAdapter( + mLog, cw, internalClassName, delegateMethods); + + ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); + cr.accept(cv, 0 /* flags */); + + // Load the generated class in a different class loader and try it again + + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + Class<?> clazz2 = loadClass(NATIVE_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.add(NATIVE_CLASS_NAME, cw); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } + } + + /** + * {@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 = NATIVE_CLASS_NAME.replace('.', '/'); + + HashSet<String> delegateMethods = new HashSet<String>(); + delegateMethods.add("<init>"); + DelegateClassAdapter cv = new DelegateClassAdapter( + mLog, cw, internalClassName, delegateMethods); + + ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); + cr.accept(cv, 0 /* flags */); + } + + @Test + public void testDelegateNative() throws Throwable { + ClassWriter cw = new ClassWriter(0 /*flags*/); + String internalClassName = NATIVE_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(NATIVE_CLASS_NAME); + cr.accept(cv, 0 /* flags */); + + // Load the generated class in a different class loader and try it + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + Class<?> clazz2 = loadClass(NATIVE_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.add(NATIVE_CLASS_NAME, cw); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } + } + + @Test + public void testDelegateInner() throws Throwable { + // We'll delegate the "get" method of both the inner and outer class. + HashSet<String> delegateMethods = new HashSet<String>(); + delegateMethods.add("get"); + + // Generate the delegate for the outer class. + ClassWriter cwOuter = new ClassWriter(0 /*flags*/); + String outerClassName = OUTER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvOuter = new DelegateClassAdapter( + mLog, cwOuter, outerClassName, delegateMethods); + ClassReader cr = new ClassReader(OUTER_CLASS_NAME); + cr.accept(cvOuter, 0 /* flags */); + + // Generate the delegate for the inner class. + ClassWriter cwInner = new ClassWriter(0 /*flags*/); + String innerClassName = INNER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvInner = new DelegateClassAdapter( + mLog, cwInner, innerClassName, delegateMethods); + cr = new ClassReader(INNER_CLASS_NAME); + cr.accept(cvInner, 0 /* flags */); + + // Load the generated classes in a different class loader and try them + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + + // Check the outer class + Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME); + Object o2 = outerClazz2.newInstance(); + assertNotNull(o2); + + // The original Outer.get returns 1+10+20, + // but the delegate makes it return 4+10+20 + assertEquals(4+10+20, callGet(o2, 10, 20)); + + // Check the inner class. Since it's not a static inner class, we need + // to use the hidden constructor that takes the outer class as first parameter. + Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME); + Constructor<?> innerCons = innerClazz2.getConstructor( + new Class<?>[] { outerClazz2 }); + Object i2 = innerCons.newInstance(new Object[] { o2 }); + assertNotNull(i2); + + // The original Inner.get returns 3+10+20, + // but the delegate makes it return 6+10+20 + assertEquals(6+10+20, callGet(i2, 10, 20)); + } + }; + cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray()); + cl2.add(INNER_CLASS_NAME, cwInner.toByteArray()); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } + } + + //------- + + /** + * A class loader than can define and instantiate our modified classes. + * <p/> + * The trick here is that this class loader will test our <em>modified</em> version + * of the classes, the one with the delegate calls. + * <p/> + * 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 Map<String, byte[]> mClassDefs = new HashMap<String, byte[]>(); + + public ClassLoader2() { + super(null); + } + + public ClassLoader2 add(String className, byte[] definition) { + mClassDefs.put(className, definition); + return this; + } + + public ClassLoader2 add(String className, ClassWriter rewrittenClass) { + mClassDefs.put(className, rewrittenClass.toByteArray()); + return this; + } + + private Set<Entry<String, byte[]>> getByteCode() { + return mClassDefs.entrySet(); + } + + @SuppressWarnings("unused") + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + try { + return super.findClass(name); + } catch (ClassNotFoundException e) { + + byte[] def = mClassDefs.get(name); + if (def != null) { + // Load the modified ClassWithNative from its bytes representation. + return defineClass(name, def, 0, def.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 OuterClass#get()} or {@link InnerClass#get() }via reflection. + */ + public int callGet(Object instance, int a, long b) throws Exception { + Method m = instance.getClass().getMethod("get", + new Class<?>[] { int.class, long.class } ); + + Object result = m.invoke(instance, new Object[] { a, b }); + return ((Integer) result).intValue(); + } + + /** + * 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; + } + + /** + * For debugging, it's useful to dump the content of the generated classes + * along with the exception that was generated. + * + * 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. Since we don't + * want that dependency in the source code, we only put it manually for development and + * access the TraceClassVisitor via reflection if present. + * + * @param t The exception thrown by {@link ClassLoader2#testModifiedInstance()} + * @param cl2 The {@link ClassLoader2} instance with the generated bytecode. + * @return Either original {@code t} or a new wrapper {@link Throwable} + */ + private Throwable dumpGeneratedClass(Throwable t, ClassLoader2 cl2) { + try { + // For debugging, dump the bytecode of the class in case of unexpected error + // if we can find the TraceClassVisitor class. + Class<?> tcvClass = Class.forName("org.objectweb.asm.util.TraceClassVisitor"); + + StringBuilder sb = new StringBuilder(); + sb.append('\n').append(t.getClass().getCanonicalName()); + if (t.getMessage() != null) { + sb.append(": ").append(t.getMessage()); + } + + for (Entry<String, byte[]> entry : cl2.getByteCode()) { + String className = entry.getKey(); + byte[] bytes = entry.getValue(); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw); + Constructor<?> cons = tcvClass.getConstructor(new Class<?>[] { pw.getClass() }); + Object tcv = cons.newInstance(new Object[] { pw }); + ClassReader cr2 = new ClassReader(bytes); + cr2.accept((ClassVisitor) tcv, 0 /* flags */); + + sb.append("\nBytecode dump: <").append(className).append(">:\n") + .append(sw.toString()); + } + + // Re-throw exception with new message + RuntimeException ex = new RuntimeException(sb.toString(), t); + return ex; + } catch (Throwable ignore) { + // In case of problem, just throw the original exception as-is. + return t; + } + } + +} 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'); + } +} diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java new file mode 100644 index 0000000..c314853 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java @@ -0,0 +1,45 @@ +/* + * 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.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * Dummy test class with a native method. + * The native method is not defined and any attempt to invoke it will + * throw an {@link UnsatisfiedLinkError}. + * + * Used by {@link DelegateClassAdapterTest}. + */ +public class ClassWithNative { + public ClassWithNative() { + } + + public int add(int a, int b) { + return a + b; + } + + // Note: it's good to have a long or double for testing parameters since they take + // 2 slots in the stack/locals maps. + + 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); +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java new file mode 100644 index 0000000..a3d4dc6 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java @@ -0,0 +1,34 @@ +/* + * 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.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * The delegate that receives the call to {@link ClassWithNative_Delegate}'s overridden methods. + * + * Used by {@link DelegateClassAdapterTest}. + */ +public 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/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java new file mode 100644 index 0000000..9dc2f69 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 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.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * Test class with an inner class. + * + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass { + private int mOuterValue = 1; + public OuterClass() { + } + + // Outer.get returns 1 + a + b + // Note: it's good to have a long or double for testing parameters since they take + // 2 slots in the stack/locals maps. + public int get(int a, long b) { + return mOuterValue + a + (int) b; + } + + public class InnerClass { + public InnerClass() { + } + + // Inner.get returns 1+2=3 + a + b + public int get(int a, long b) { + return 2 + mOuterValue + a + (int) b; + } + } +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java new file mode 100644 index 0000000..3252d87 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 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.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass_Delegate { + // The delegate override of Outer.get returns 4 + a + b + public static int get(OuterClass instance, int a, long b) { + return 4 + a + (int) b; + } +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java new file mode 100644 index 0000000..b472220 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 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.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; +import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass; + +/** + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass_InnerClass_Delegate { + // The delegate override of Inner.get return 6 + a + b + public static int get(OuterClass outer, InnerClass inner, int a, long b) { + return 6 + a + (int) b; + } +} |