summaryrefslogtreecommitdiffstats
path: root/tools/layoutlib
diff options
context:
space:
mode:
authorAdam Lesinski <adamlesinski@google.com>2014-01-23 18:17:42 -0800
committerAdam Lesinski <adamlesinski@google.com>2014-01-27 10:31:04 -0800
commit282e181b58cf72b6ca770dc7ca5f91f135444502 (patch)
treee313e7ab30ff4679562efa37bde29cfcb9e375d3 /tools/layoutlib
parent7023df08f14ec5dee76ac54c03e870f84e297636 (diff)
downloadframeworks_base-282e181b58cf72b6ca770dc7ca5f91f135444502.zip
frameworks_base-282e181b58cf72b6ca770dc7ca5f91f135444502.tar.gz
frameworks_base-282e181b58cf72b6ca770dc7ca5f91f135444502.tar.bz2
Revert "Move frameworks/base/tools/ to frameworks/tools/"
This reverts commit 9f6a119c8aa276432ece4fe2118bd8a3c9b1067e.
Diffstat (limited to 'tools/layoutlib')
-rw-r--r--tools/layoutlib/.gitignore1
-rw-r--r--tools/layoutlib/Android.mk65
-rw-r--r--tools/layoutlib/README4
-rw-r--r--tools/layoutlib/bridge/.classpath11
-rw-r--r--tools/layoutlib/bridge/.project17
-rw-r--r--tools/layoutlib/bridge/.settings/README.txt2
-rw-r--r--tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs93
-rw-r--r--tools/layoutlib/bridge/Android.mk38
-rw-r--r--tools/layoutlib/bridge/resources/bars/action_bar.xml7
-rw-r--r--tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.pngbin0 -> 1053 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.pngbin0 -> 1064 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.pngbin0 -> 711 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.pngbin0 -> 1195 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.pngbin0 -> 3233 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.pngbin0 -> 774 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.pngbin0 -> 836 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.pngbin0 -> 591 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.pngbin0 -> 885 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.pngbin0 -> 204 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/navigation_bar.xml20
-rw-r--r--tools/layoutlib/bridge/resources/bars/status_bar.xml15
-rw-r--r--tools/layoutlib/bridge/resources/bars/title_bar.xml6
-rw-r--r--tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.pngbin0 -> 1421 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.pngbin0 -> 1421 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.pngbin0 -> 749 bytes
-rw-r--r--tools/layoutlib/bridge/src/android/animation/AnimationThread.java177
-rw-r--r--tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java59
-rw-r--r--tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java104
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java53
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeResources.java695
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java908
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java67
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java30
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java70
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java177
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java252
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java592
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java64
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java1362
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java64
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java70
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java71
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java97
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java71
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java89
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java71
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java64
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java65
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java212
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java69
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java70
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java240
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java64
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java1129
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java226
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java64
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java1284
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java72
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java69
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java815
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java70
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java71
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java140
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java215
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java64
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java484
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java105
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java71
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java222
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java27
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java211
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java69
-rw-r--r--tools/layoutlib/bridge/src/android/os/Build_Delegate.java48
-rw-r--r--tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java80
-rw-r--r--tools/layoutlib/bridge/src/android/os/Handler_Delegate.java57
-rw-r--r--tools/layoutlib/bridge/src/android/os/Looper_Accessor.java47
-rw-r--r--tools/layoutlib/bridge/src/android/os/ServiceManager.java72
-rw-r--r--tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java106
-rw-r--r--tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java37
-rw-r--r--tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java37
-rw-r--r--tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java283
-rw-r--r--tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java132
-rw-r--r--tools/layoutlib/bridge/src/android/util/Log_Delegate.java51
-rw-r--r--tools/layoutlib/bridge/src/android/util/LruCache.java391
-rw-r--r--tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java48
-rw-r--r--tools/layoutlib/bridge/src/android/view/BridgeInflater.java272
-rw-r--r--tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java33
-rw-r--r--tools/layoutlib/bridge/src/android/view/Display_Delegate.java36
-rw-r--r--tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java502
-rw-r--r--tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java199
-rw-r--r--tools/layoutlib/bridge/src/android/view/SurfaceView.java112
-rw-r--r--tools/layoutlib/bridge/src/android/view/ViewConfiguration_Accessor.java29
-rw-r--r--tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java34
-rw-r--r--tools/layoutlib/bridge/src/android/view/View_Delegate.java34
-rw-r--r--tools/layoutlib/bridge/src/android/view/WindowManagerGlobal_Delegate.java43
-rw-r--r--tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java137
-rw-r--r--tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java27
-rw-r--r--tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java49
-rw-r--r--tools/layoutlib/bridge/src/android/webkit/WebView.java254
-rw-r--r--tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java72
-rw-r--r--tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java111
-rw-r--r--tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java74
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java621
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java51
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java196
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java47
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java137
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java120
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java1427
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java230
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java164
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java122
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java107
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java209
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java494
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java64
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java307
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java48
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java54
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java55
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java47
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java146
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java378
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java803
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java142
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java49
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java380
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java133
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java1471
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java493
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java70
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java247
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java121
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java196
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java23
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java76
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java376
-rw-r--r--tools/layoutlib/bridge/src/com/google/android/maps/MapView.java121
-rw-r--r--tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java225
-rw-r--r--tools/layoutlib/bridge/tests/.classpath11
-rw-r--r--tools/layoutlib/bridge/tests/.project17
-rw-r--r--tools/layoutlib/bridge/tests/Android.mk32
-rw-r--r--tools/layoutlib/bridge/tests/res/com/android/layoutlib/testdata/layout1.xml49
-rw-r--r--tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java58
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java197
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java121
-rw-r--r--tools/layoutlib/create/.classpath9
-rw-r--r--tools/layoutlib/create/.project17
-rw-r--r--tools/layoutlib/create/.settings/README.txt2
-rw-r--r--tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs93
-rw-r--r--tools/layoutlib/create/Android.mk28
-rw-r--r--tools/layoutlib/create/README.txt240
-rw-r--r--tools/layoutlib/create/manifest.txt1
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java27
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java35
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java50
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java845
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java370
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java102
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java207
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java133
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java461
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java787
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java65
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java72
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java32
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java212
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java97
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java76
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java151
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java463
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java376
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java183
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java227
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java119
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java102
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java463
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java88
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java43
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java120
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java45
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java34
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java53
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java34
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java30
-rw-r--r--tools/layoutlib/create/tests/data/mock_android.jarbin0 -> 9075 bytes
-rw-r--r--tools/layoutlib/create/tests/data/mock_android.jardesc18
-rw-r--r--tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java90
-rw-r--r--tools/layoutlib/create/tests/mock_android/view/View.java21
-rw-r--r--tools/layoutlib/create/tests/mock_android/view/ViewGroup.java29
-rw-r--r--tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java27
-rw-r--r--tools/layoutlib/create/tests/mock_android/widget/TableLayout.java27
192 files changed, 32160 insertions, 0 deletions
diff --git a/tools/layoutlib/.gitignore b/tools/layoutlib/.gitignore
new file mode 100644
index 0000000..c5e82d7
--- /dev/null
+++ b/tools/layoutlib/.gitignore
@@ -0,0 +1 @@
+bin \ No newline at end of file
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
new file mode 100644
index 0000000..4e73568
--- /dev/null
+++ b/tools/layoutlib/Android.mk
@@ -0,0 +1,65 @@
+#
+# 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.
+#
+LOCAL_PATH := $(my-dir)
+include $(CLEAR_VARS)
+
+#
+# Define rules to build temp_layoutlib.jar, which contains a subset of
+# the classes in framework.jar. The layoutlib_create tool is used to
+# transform the framework jar into the temp_layoutlib jar.
+#
+
+# We need to process the framework classes.jar file, but we can't
+# depend directly on it (private vars won't be inherited correctly).
+# So, we depend on framework's BUILT file.
+built_framework_dep := $(call java-lib-deps,framework-base)
+built_framework_classes := $(call java-lib-files,framework-base)
+
+built_core_dep := $(call java-lib-deps,core)
+built_core_classes := $(call java-lib-files,core)
+
+built_layoutlib_create_jar := $(call intermediates-dir-for, \
+ JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar
+
+# This is mostly a copy of config/host_java_library.mk
+LOCAL_MODULE := temp_layoutlib
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_MODULE_SUFFIX := $(COMMON_JAVA_PACKAGE_SUFFIX)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_BUILT_MODULE_STEM := javalib.jar
+
+#######################################
+include $(BUILD_SYSTEM)/base_rules.mk
+#######################################
+
+$(LOCAL_BUILT_MODULE): $(built_core_dep) \
+ $(built_framework_dep) \
+ $(built_layoutlib_create_jar)
+ $(hide) echo "host layoutlib_create: $@"
+ $(hide) mkdir -p $(dir $@)
+ $(hide) rm -f $@
+ $(hide) ls -l $(built_framework_classes)
+ $(hide) java -jar $(built_layoutlib_create_jar) \
+ $@ \
+ $(built_core_classes) \
+ $(built_framework_classes)
+ $(hide) ls -l $(built_framework_classes)
+
+
+#
+# Include the subdir makefiles.
+#
+include $(call all-makefiles-under,$(LOCAL_PATH))
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
new file mode 100644
index 0000000..3c124d9
--- /dev/null
+++ b/tools/layoutlib/bridge/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry excluding="org/kxml2/io/" kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/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/prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/tools-common/tools-common-prebuilt.jar"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/layoutlib/bridge/.project b/tools/layoutlib/bridge/.project
new file mode 100644
index 0000000..e36e71b
--- /dev/null
+++ b/tools/layoutlib/bridge/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>layoutlib_bridge</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/tools/layoutlib/bridge/.settings/README.txt b/tools/layoutlib/bridge/.settings/README.txt
new file mode 100644
index 0000000..9120b20
--- /dev/null
+++ b/tools/layoutlib/bridge/.settings/README.txt
@@ -0,0 +1,2 @@
+Copy this in eclipse project as a .settings folder at the root.
+This ensure proper compilation compliance and warning/error levels. \ No newline at end of file
diff --git a/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs b/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..5381a0e
--- /dev/null
+++ b/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,93 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled
+org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk
new file mode 100644
index 0000000..687a91f
--- /dev/null
+++ b/tools/layoutlib/bridge/Android.mk
@@ -0,0 +1,38 @@
+#
+# 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.
+#
+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-prebuilt \
+ tools-common-prebuilt
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ temp_layoutlib \
+ ninepatch-prebuilt
+
+LOCAL_MODULE := layoutlib
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
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..7adc5af
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/action_bar.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <include layout="@android:layout/action_bar_home" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..84e6bc8
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..38e4f45
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..bf9f300
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png
Binary files differ
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
new file mode 100644
index 0000000..bd44b52
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png
Binary files differ
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
new file mode 100644
index 0000000..a4be298
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..a00bc5b
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..dc3183b
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..b07f611
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png
Binary files differ
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
new file mode 100644
index 0000000..c629387
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png
Binary files differ
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
new file mode 100644
index 0000000..eb7c1a4
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/navigation_bar.xml b/tools/layoutlib/bridge/resources/bars/navigation_bar.xml
new file mode 100644
index 0000000..599ca08
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/navigation_bar.xml
@@ -0,0 +1,20 @@
+<?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"/>
+ <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"/>
+</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/status_bar.xml b/tools/layoutlib/bridge/resources/bars/status_bar.xml
new file mode 100644
index 0000000..d3c492e
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/status_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/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/resources/bars/xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..bd60cd6
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..c5bc5c9
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..f621d9c
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
new file mode 100644
index 0000000..b10ec9f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
@@ -0,0 +1,177 @@
+/*
+ * 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.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 com.android.layoutlib.bridge.impl.RenderSessionImpl;
+
+import android.os.Handler;
+import android.os.Handler_Delegate;
+import android.os.Handler_Delegate.IHandlerCallback;
+import android.os.Message;
+
+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;
+ }
+
+ @Override
+ 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 {
+ /* FIXME: The ANIMATION_FRAME message no longer exists. Instead, the
+ * animation timing loop is completely based on a Choreographer objects
+ * that schedules animation and drawing frames. The animation handler is
+ * no longer even a handler; it is just a Runnable enqueued on the Choreographer.
+ Handler_Delegate.setCallback(new IHandlerCallback() {
+ @Override
+ 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/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/BridgeAssetManager.java b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
new file mode 100644
index 0000000..a953918
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
@@ -0,0 +1,53 @@
+/*
+ * 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.content.res;
+
+import com.android.layoutlib.bridge.Bridge;
+
+import android.content.res.AssetManager;
+
+public class BridgeAssetManager extends AssetManager {
+
+ /**
+ * This initializes the static field {@link AssetManager#mSystem} which is used
+ * by methods who get a global asset manager using {@link AssetManager#getSystem()}.
+ * <p/>
+ * They will end up using our bridge asset manager.
+ * <p/>
+ * {@link Bridge} calls this method after setting up a new bridge.
+ */
+ public static AssetManager initSystem() {
+ if (!(AssetManager.sSystem instanceof BridgeAssetManager)) {
+ // Note that AssetManager() creates a system AssetManager and we override it
+ // with our BridgeAssetManager.
+ AssetManager.sSystem = new BridgeAssetManager();
+ AssetManager.sSystem.makeStringBlocks(false);
+ }
+ return AssetManager.sSystem;
+ }
+
+ /**
+ * Clears the static {@link AssetManager#sSystem} to make sure we don't leave objects
+ * around that would prevent us from unloading the library.
+ */
+ public static void clearSystem() {
+ AssetManager.sSystem = null;
+ }
+
+ private BridgeAssetManager() {
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
new file mode 100644
index 0000000..8794452
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
@@ -0,0 +1,695 @@
+/*
+ * 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.content.res;
+
+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.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.ninepatch.NinePatch;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.ViewGroup.LayoutParams;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+/**
+ *
+ */
+public final class BridgeResources extends Resources {
+
+ private BridgeContext mContext;
+ private IProjectCallback mProjectCallback;
+ 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/>
+ * They will end up using our bridge resources.
+ * <p/>
+ * {@link Bridge} calls this method after setting up a new bridge.
+ */
+ public static Resources initSystem(BridgeContext context,
+ AssetManager assets,
+ DisplayMetrics metrics,
+ Configuration config,
+ IProjectCallback projectCallback) {
+ return Resources.mSystem = new BridgeResources(context,
+ assets,
+ metrics,
+ config,
+ projectCallback);
+ }
+
+ /**
+ * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects
+ * around that would prevent us from unloading the library.
+ */
+ public static void disposeSystem() {
+ if (Resources.mSystem instanceof BridgeResources) {
+ ((BridgeResources)(Resources.mSystem)).mContext = null;
+ ((BridgeResources)(Resources.mSystem)).mProjectCallback = null;
+ }
+ Resources.mSystem = null;
+ }
+
+ private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics,
+ Configuration config, IProjectCallback projectCallback) {
+ super(assets, metrics, config);
+ mContext = context;
+ mProjectCallback = projectCallback;
+ }
+
+ public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile) {
+ return new BridgeTypedArray(this, mContext, numEntries, platformFile);
+ }
+
+ private Pair<String, ResourceValue> getResourceValue(int id, boolean[] platformResFlag_out) {
+ // first get the String related to this id in the framework
+ Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+
+ if (resourceInfo != null) {
+ platformResFlag_out[0] = true;
+ String attributeName = resourceInfo.getSecond();
+
+ return Pair.of(attributeName, mContext.getRenderResources().getFrameworkResource(
+ resourceInfo.getFirst(), attributeName));
+ }
+
+ // didn't find a match in the framework? look in the project.
+ if (mProjectCallback != null) {
+ resourceInfo = mProjectCallback.resolveResourceId(id);
+
+ if (resourceInfo != null) {
+ platformResFlag_out[0] = false;
+ String attributeName = resourceInfo.getSecond();
+
+ return Pair.of(attributeName, mContext.getRenderResources().getProjectResource(
+ resourceInfo.getFirst(), attributeName));
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public Drawable getDrawable(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ return ResourceHelper.getDrawable(value.getSecond(), mContext);
+ }
+
+ // 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 int getColor(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ try {
+ return ResourceHelper.getColor(value.getSecond().getValue());
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e,
+ null /*data*/);
+ return 0;
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @Override
+ public ColorStateList getColorStateList(int id) throws NotFoundException {
+ Pair<String, ResourceValue> resValue = getResourceValue(id, mPlatformResourceFlag);
+
+ if (resValue != null) {
+ ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
+ mContext);
+ if (stateList != null) {
+ return stateList;
+ }
+ }
+
+ // 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 CharSequence getText(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ return v;
+ }
+ }
+ }
+
+ // 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 getLayout(int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
+
+ if (v != null) {
+ ResourceValue value = v.getSecond();
+ XmlPullParser parser = null;
+
+ try {
+ // check if the current parser can provide us with a custom parser.
+ if (mPlatformResourceFlag[0] == false) {
+ parser = mProjectCallback.getParser(value);
+ }
+
+ // 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 = ParserFactory.create(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 {
+ Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
+
+ if (v != null) {
+ ResourceValue value = v.getSecond();
+ 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 = ParserFactory.create(xml);
+
+ 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 TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
+ return mContext.obtainStyledAttributes(set, attrs);
+ }
+
+ @Override
+ public TypedArray obtainTypedArray(int id) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public float getDimension(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ if (v.equals(BridgeConstants.MATCH_PARENT) ||
+ v.equals(BridgeConstants.FILL_PARENT)) {
+ return LayoutParams.MATCH_PARENT;
+ } else if (v.equals(BridgeConstants.WRAP_CONTENT)) {
+ return LayoutParams.WRAP_CONTENT;
+ }
+
+ if (ResourceHelper.parseFloatAttribute(
+ value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
+ mTmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return mTmpValue.getDimension(getDisplayMetrics());
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @Override
+ public int getDimensionPixelOffset(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ if (ResourceHelper.parseFloatAttribute(
+ value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
+ mTmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelOffset(mTmpValue.data,
+ getDisplayMetrics());
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @Override
+ public int getDimensionPixelSize(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ if (ResourceHelper.parseFloatAttribute(
+ value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
+ mTmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(mTmpValue.data,
+ getDisplayMetrics());
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @Override
+ public int getInteger(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ int radix = 10;
+ if (v.startsWith("0x")) {
+ v = v.substring(2);
+ radix = 16;
+ }
+ try {
+ return Integer.parseInt(v, radix);
+ } catch (NumberFormatException e) {
+ // return exception below
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @Override
+ public boolean getBoolean(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ return Boolean.parseBoolean(v);
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return false;
+ }
+
+ @Override
+ public String getResourceEntryName(int resid) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getResourceName(int resid) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getResourceTypeName(int resid) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getString(int id, Object... formatArgs) throws NotFoundException {
+ String s = getString(id);
+ if (s != null) {
+ return String.format(s, formatArgs);
+
+ }
+
+ // 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 String getString(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null && value.getSecond().getValue() != null) {
+ return value.getSecond().getValue();
+ }
+
+ // 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 void getValue(int id, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ String v = value.getSecond().getValue();
+
+ if (v != null) {
+ if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue,
+ false /*requireUnit*/)) {
+ return;
+ }
+
+ // else it's a string
+ outValue.type = TypedValue.TYPE_STRING;
+ outValue.string = v;
+ return;
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+ }
+
+ @Override
+ public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public XmlResourceParser getXml(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ String v = value.getSecond().getValue();
+
+ if (v != null) {
+ // check this is a file
+ File f = new File(v);
+ if (f.isFile()) {
+ try {
+ XmlPullParser parser = ParserFactory.create(f);
+
+ return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ } catch (XmlPullParserException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ } catch (FileNotFoundException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ }
+ }
+ }
+ }
+
+ // 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 loadXmlResourceParser(String file, int id,
+ int assetCookie, String type) throws NotFoundException {
+ // even though we know the XML file to load directly, we still need to resolve the
+ // id so that we can know if it's a platform or project resource.
+ // (mPlatformResouceFlag will get the result and will be used later).
+ getResourceValue(id, mPlatformResourceFlag);
+
+ File f = new File(file);
+ try {
+ XmlPullParser parser = ParserFactory.create(f);
+
+ return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ } catch (XmlPullParserException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ } catch (FileNotFoundException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ }
+ }
+
+
+ @Override
+ public InputStream openRawResource(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ String path = value.getSecond().getValue();
+
+ if (path != null) {
+ // check this is a file
+ 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();
+ newE.initCause(e);
+ throw newE;
+ }
+ }
+ }
+ }
+
+ // 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 InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
+ getValue(id, value, true);
+
+ 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();
+ exception.initCause(e);
+ throw exception;
+ }
+ }
+
+ throw new NotFoundException();
+ }
+
+ @Override
+ public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource type.
+ * @param id the id of the resource
+ * @throws NotFoundException
+ */
+ private void throwException(int id) throws NotFoundException {
+ // first get the String related to this id in the framework
+ 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.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.getFirst(), id, resourceInfo.getSecond());
+ } else {
+ message = String.format(
+ "Could not resolve resource value: 0x%1$X.", id);
+ }
+
+ throw new NotFoundException(message);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
new file mode 100644
index 0000000..446d139
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -0,0 +1,908 @@
+/*
+ * 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.content.res;
+
+import com.android.ide.common.rendering.api.AttrResourceValue;
+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.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.resources.ResourceType;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.LayoutInflater_Delegate;
+import android.view.ViewGroup.LayoutParams;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Custom implementation of TypedArray to handle non compiled resources.
+ */
+public final class BridgeTypedArray extends TypedArray {
+
+ private final BridgeResources mBridgeResources;
+ private final BridgeContext mContext;
+ private final boolean mPlatformFile;
+
+ private ResourceValue[] mResourceData;
+ private String[] mNames;
+ private boolean[] mIsFramework;
+
+ public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len,
+ boolean platformFile) {
+ super(null, null, null, 0);
+ mBridgeResources = resources;
+ mContext = context;
+ mPlatformFile = platformFile;
+ mResourceData = new ResourceValue[len];
+ mNames = new String[len];
+ mIsFramework = new boolean[len];
+ }
+
+ /**
+ * A bridge-specific method that sets a value in the type array
+ * @param index the index of the value in the TypedArray
+ * @param name the name of the attribute
+ * @param isFramework whether the attribute is in the android namespace.
+ * @param value the value of the attribute
+ */
+ public void bridgeSetValue(int index, String name, boolean isFramework, ResourceValue value) {
+ mResourceData[index] = value;
+ mNames[index] = name;
+ mIsFramework[index] = isFramework;
+ }
+
+ /**
+ * 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.
+ */
+ public void sealArray() {
+ // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt
+ // first count the array size
+ int count = 0;
+ for (ResourceValue data : mResourceData) {
+ if (data != null) {
+ count++;
+ }
+ }
+
+ // allocate the table with an extra to store the size
+ mIndices = new int[count+1];
+ mIndices[0] = count;
+
+ // fill the array with the indices.
+ int index = 1;
+ for (int i = 0 ; i < mResourceData.length ; i++) {
+ if (mResourceData[i] != null) {
+ mIndices[index++] = i;
+ }
+ }
+ }
+
+ /**
+ * Return the number of values in this array.
+ */
+ @Override
+ public int length() {
+ return mResourceData.length;
+ }
+
+ /**
+ * Return the Resources object this array was loaded from.
+ */
+ @Override
+ public Resources getResources() {
+ return mBridgeResources;
+ }
+
+ /**
+ * Retrieve the styled string value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return CharSequence holding string data. May be styled. Returns
+ * null if the attribute is not defined.
+ */
+ @Override
+ public CharSequence getText(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (mResourceData[index] != null) {
+ // FIXME: handle styled strings!
+ return mResourceData[index].getValue();
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve the string value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return String holding string data. Any styling information is
+ * removed. Returns null if the attribute is not defined.
+ */
+ @Override
+ public String getString(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (mResourceData[index] != null) {
+ return mResourceData[index].getValue();
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve the boolean value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined.
+ *
+ * @return Attribute boolean value, or defValue if not defined.
+ */
+ @Override
+ public boolean getBoolean(int index, boolean defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ String s = mResourceData[index].getValue();
+ if (s != null) {
+ return XmlUtils.convertValueToBoolean(s, defValue);
+ }
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve the integer value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined.
+ *
+ * @return Attribute int value, or defValue if not defined.
+ */
+ @Override
+ public int getInt(int index, int defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ String s = mResourceData[index].getValue();
+
+ if (RenderResources.REFERENCE_NULL.equals(s)) {
+ return defValue;
+ }
+
+ if (s == null || s.length() == 0) {
+ return defValue;
+ }
+
+ try {
+ return XmlUtils.convertValueToInt(s, defValue);
+ } catch (NumberFormatException e) {
+ // pass
+ }
+
+ // Field is not null and is not an integer.
+ // Check for possible constants and try to find them.
+ // Get the map of attribute-constant -> IntegerValue
+ Map<String, Integer> map = null;
+ if (mIsFramework[index]) {
+ map = Bridge.getEnumValues(mNames[index]);
+ } else {
+ // get the styleable matching the resolved name
+ RenderResources res = mContext.getRenderResources();
+ ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]);
+ if (attr instanceof AttrResourceValue) {
+ map = ((AttrResourceValue) attr).getAttributeValues();
+ }
+ }
+
+ if (map != null) {
+ // accumulator to store the value of the 1+ constants.
+ int result = 0;
+
+ // split the value in case this is a mix of several flags.
+ String[] keywords = s.split("\\|");
+ for (String keyword : keywords) {
+ Integer i = map.get(keyword.trim());
+ if (i != null) {
+ result |= i.intValue();
+ } else {
+ 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;
+ }
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve the float value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Attribute float value, or defValue if not defined..
+ */
+ @Override
+ public float getFloat(int index, float defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ String s = mResourceData[index].getValue();
+
+ if (s != null) {
+ try {
+ return Float.parseFloat(s);
+ } catch (NumberFormatException e) {
+ 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.
+ }
+ }
+ return defValue;
+ }
+
+ /**
+ * Retrieve the color value for the attribute at <var>index</var>. If
+ * the attribute references a color resource holding a complex
+ * {@link android.content.res.ColorStateList}, then the default color from
+ * the set is returned.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute color value, or defValue if not defined.
+ */
+ @Override
+ public int getColor(int index, int defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ ColorStateList colorStateList = ResourceHelper.getColorStateList(
+ mResourceData[index], mContext);
+ if (colorStateList != null) {
+ return colorStateList.getDefaultColor();
+ }
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve the ColorStateList for the attribute at <var>index</var>.
+ * The value may be either a single solid color or a reference to
+ * a color or complex {@link android.content.res.ColorStateList} description.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return ColorStateList for the attribute, or null if not defined.
+ */
+ @Override
+ public ColorStateList getColorStateList(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (mResourceData[index] == null) {
+ return null;
+ }
+
+ ResourceValue resValue = mResourceData[index];
+ String value = resValue.getValue();
+
+ if (value == null) {
+ return null;
+ }
+
+ if (RenderResources.REFERENCE_NULL.equals(value)) {
+ return null;
+ }
+
+ // let the framework inflate the ColorStateList from the XML file.
+ File f = new File(value);
+ if (f.isFile()) {
+ try {
+ XmlPullParser parser = ParserFactory.create(f);
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * Retrieve the integer value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute integer value, or defValue if not defined.
+ */
+ @Override
+ public int getInteger(int index, int defValue) {
+ return getInt(index, defValue);
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var>. Unit
+ * conversions are based on the current {@link DisplayMetrics}
+ * associated with the resources this {@link TypedArray} object
+ * came from.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric, or defValue if not defined.
+ *
+ * @see #getDimensionPixelOffset
+ * @see #getDimensionPixelSize
+ */
+ @Override
+ public float getDimension(int index, float defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ String s = mResourceData[index].getValue();
+
+ if (s == null) {
+ return defValue;
+ } else if (s.equals(BridgeConstants.MATCH_PARENT) ||
+ s.equals(BridgeConstants.FILL_PARENT)) {
+ return LayoutParams.MATCH_PARENT;
+ } else if (s.equals(BridgeConstants.WRAP_CONTENT)) {
+ return LayoutParams.WRAP_CONTENT;
+ } else if (RenderResources.REFERENCE_NULL.equals(s)) {
+ return defValue;
+ }
+
+ if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true /*requireUnit*/)) {
+ return mValue.getDimension(mBridgeResources.getDisplayMetrics());
+ }
+
+ // looks like we were unable to resolve the dimension value
+ 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;
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var> for use
+ * as an offset in raw pixels. This is the same as
+ * {@link #getDimension}, except the returned value is converted to
+ * integer pixels for you. An offset conversion involves simply
+ * truncating the base value to an integer.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels, or defValue if not defined.
+ *
+ * @see #getDimension
+ * @see #getDimensionPixelSize
+ */
+ @Override
+ public int getDimensionPixelOffset(int index, int defValue) {
+ return (int) getDimension(index, defValue);
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var> for use
+ * as a size in raw pixels. This is the same as
+ * {@link #getDimension}, except the returned value is converted to
+ * integer pixels for use as a size. A size conversion involves
+ * rounding the base value, and ensuring that a non-zero base value
+ * is at least one pixel in size.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels, or defValue if not defined.
+ *
+ * @see #getDimension
+ * @see #getDimensionPixelOffset
+ */
+ @Override
+ public int getDimensionPixelSize(int index, int defValue) {
+ try {
+ return getDimension(index);
+ } catch (RuntimeException e) {
+ if (mResourceData[index] != null) {
+ String s = mResourceData[index].getValue();
+
+ if (s != null) {
+ // looks like we were unable to resolve the dimension value
+ 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;
+ }
+ }
+
+ /**
+ * Special version of {@link #getDimensionPixelSize} for retrieving
+ * {@link android.view.ViewGroup}'s layout_width and layout_height
+ * attributes. This is only here for performance reasons; applications
+ * should use {@link #getDimensionPixelSize}.
+ *
+ * @param index Index of the attribute to retrieve.
+ * @param name Textual name of attribute for error reporting.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels.
+ */
+ @Override
+ public int getLayoutDimension(int index, String name) {
+ try {
+ // this will throw an exception
+ return getDimension(index);
+ } catch (RuntimeException e) {
+
+ if (LayoutInflater_Delegate.sIsInInclude) {
+ throw new RuntimeException();
+ }
+
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+ "You must supply a " + name + " attribute.", null);
+
+ return 0;
+ }
+ }
+
+ @Override
+ public int getLayoutDimension(int index, int defValue) {
+ return getDimensionPixelSize(index, defValue);
+ }
+
+ private int getDimension(int index) {
+ if (mResourceData[index] == null) {
+ throw new RuntimeException();
+ }
+
+ String s = mResourceData[index].getValue();
+
+ if (s == null) {
+ throw new RuntimeException();
+ } else if (s.equals(BridgeConstants.MATCH_PARENT) ||
+ s.equals(BridgeConstants.FILL_PARENT)) {
+ return LayoutParams.MATCH_PARENT;
+ } else if (s.equals(BridgeConstants.WRAP_CONTENT)) {
+ return LayoutParams.WRAP_CONTENT;
+ } else if (RenderResources.REFERENCE_NULL.equals(s)) {
+ throw new RuntimeException();
+ }
+
+ if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true /*requireUnit*/)) {
+ float f = mValue.getDimension(mBridgeResources.getDisplayMetrics());
+
+ final int res = (int)(f+0.5f);
+ if (res != 0) return res;
+ if (f == 0) return 0;
+ if (f > 0) return 1;
+ }
+
+ throw new RuntimeException();
+ }
+
+ /**
+ * Retrieve a fractional unit attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param base The base value of this fraction. In other words, a
+ * standard fraction is multiplied by this value.
+ * @param pbase The parent base value of this fraction. In other
+ * words, a parent fraction (nn%p) is multiplied by this
+ * value.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute fractional value multiplied by the appropriate
+ * base value, or defValue if not defined.
+ */
+ @Override
+ public float getFraction(int index, int base, int pbase, float defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ String value = mResourceData[index].getValue();
+ if (value == null) {
+ return defValue;
+ }
+
+ if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue,
+ false /*requireUnit*/)) {
+ return mValue.getFraction(base, pbase);
+ }
+
+ // looks like we were unable to resolve the fraction value
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+ String.format(
+ "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.",
+ value, mNames[index]), null /*data*/);
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve the resource identifier for the attribute at
+ * <var>index</var>. Note that attribute resource as resolved when
+ * the overall {@link TypedArray} object is retrieved. As a
+ * result, this function will return the resource identifier of the
+ * final resource value that was found, <em>not</em> necessarily the
+ * original resource that was specified by the attribute.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute resource identifier, or defValue if not defined.
+ */
+ @Override
+ public int getResourceId(int index, int defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ // get the Resource for this index
+ ResourceValue resValue = mResourceData[index];
+
+ // no data, return the default value.
+ if (resValue == null) {
+ return defValue;
+ }
+
+ // check if this is a style resource
+ if (resValue instanceof StyleResourceValue) {
+ // get the id that will represent this style.
+ return mContext.getDynamicIdByStyle((StyleResourceValue)resValue);
+ }
+
+ if (RenderResources.REFERENCE_NULL.equals(resValue.getValue())) {
+ return defValue;
+ }
+
+ // 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.getResourceType() != null) {
+ // if this is a framework id
+ if (mPlatformFile || resValue.isFramework()) {
+ // look for idName in the android R classes
+ return mContext.getFrameworkResourceValue(
+ resValue.getResourceType(), resValue.getName(), defValue);
+ }
+
+ // look for idName in the project R class.
+ return mContext.getProjectResourceValue(
+ resValue.getResourceType(), resValue.getName(), defValue);
+ }
+
+ // else, try to get the value, and resolve it somehow.
+ String value = resValue.getValue();
+ if (value == null) {
+ return defValue;
+ }
+
+ // if the value is just an integer, return it.
+ try {
+ int i = Integer.parseInt(value);
+ if (Integer.toString(i).equals(value)) {
+ return i;
+ }
+ } catch (NumberFormatException e) {
+ // pass
+ }
+
+ // Handle the @id/<name>, @+id/<name> and @android:id/<name>
+ // We need to return the exact value that was compiled (from the various R classes),
+ // as these values can be reused internally with calls to findViewById().
+ // There's a trick with platform layouts that not use "android:" but their IDs are in
+ // fact in the android.R and com.android.internal.R classes.
+ // The field mPlatformFile will indicate that all IDs are to be looked up in the android R
+ // classes exclusively.
+
+ // if this is a reference to an id, find it.
+ if (value.startsWith("@id/") || value.startsWith("@+") ||
+ value.startsWith("@android:id/")) {
+
+ int pos = value.indexOf('/');
+ String idName = value.substring(pos + 1);
+
+ // if this is a framework id
+ if (mPlatformFile || value.startsWith("@android") || value.startsWith("@+android")) {
+ // look for idName in the android R classes
+ return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue);
+ }
+
+ // look for idName in the project R class.
+ return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue);
+ }
+
+ // not a direct id valid reference? resolve it
+ Integer idValue = null;
+
+ if (resValue.isFramework()) {
+ idValue = Bridge.getResourceId(resValue.getResourceType(),
+ resValue.getName());
+ } else {
+ idValue = mContext.getProjectCallback().getResourceId(
+ resValue.getResourceType(), resValue.getName());
+ }
+
+ if (idValue != null) {
+ return idValue.intValue();
+ }
+
+ 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;
+ }
+
+ /**
+ * Retrieve the Drawable for the attribute at <var>index</var>. This
+ * gets the resource ID of the selected attribute, and uses
+ * {@link Resources#getDrawable Resources.getDrawable} of the owning
+ * Resources object to retrieve its Drawable.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Drawable for the attribute, or null if not defined.
+ */
+ @Override
+ public Drawable getDrawable(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (mResourceData[index] == null) {
+ return null;
+ }
+
+ ResourceValue value = mResourceData[index];
+ String stringValue = value.getValue();
+ if (stringValue == null || RenderResources.REFERENCE_NULL.equals(stringValue)) {
+ return null;
+ }
+
+ return ResourceHelper.getDrawable(value, mContext);
+ }
+
+
+ /**
+ * Retrieve the CharSequence[] for the attribute at <var>index</var>.
+ * This gets the resource ID of the selected attribute, and uses
+ * {@link Resources#getTextArray Resources.getTextArray} of the owning
+ * Resources object to retrieve its String[].
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return CharSequence[] for the attribute, or null if not defined.
+ */
+ @Override
+ public CharSequence[] getTextArray(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (mResourceData[index] == null) {
+ return null;
+ }
+
+ String value = mResourceData[index].getValue();
+ if (value != null) {
+ if (RenderResources.REFERENCE_NULL.equals(value)) {
+ return null;
+ }
+
+ return new CharSequence[] { value };
+ }
+
+ 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;
+ }
+
+ /**
+ * Retrieve the raw TypedValue for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param outValue TypedValue object in which to place the attribute's
+ * data.
+ *
+ * @return Returns true if the value was retrieved, else false.
+ */
+ @Override
+ public boolean getValue(int index, TypedValue outValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return false;
+ }
+
+ if (mResourceData[index] == null) {
+ return false;
+ }
+
+ String s = mResourceData[index].getValue();
+
+ return ResourceHelper.parseFloatAttribute(mNames[index], s, outValue,
+ false /*requireUnit*/);
+ }
+
+ /**
+ * Determines whether there is an attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return True if the attribute has a value, false otherwise.
+ */
+ @Override
+ public boolean hasValue(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return false;
+ }
+
+ return mResourceData[index] != null;
+ }
+
+ /**
+ * Retrieve the raw TypedValue for the attribute at <var>index</var>
+ * and return a temporary object holding its data. This object is only
+ * valid until the next call on to {@link TypedArray}.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Returns a TypedValue object if the attribute is defined,
+ * containing its data; otherwise returns null. (You will not
+ * receive a TypedValue whose type is TYPE_NULL.)
+ */
+ @Override
+ public TypedValue peekValue(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (getValue(index, mValue)) {
+ return mValue;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a message about the parser state suitable for printing error messages.
+ */
+ @Override
+ public String getPositionDescription() {
+ return "<internal -- stub if needed>";
+ }
+
+ /**
+ * Give back a previously retrieved TypedArray, for later re-use.
+ */
+ @Override
+ public void recycle() {
+ // pass
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(mResourceData);
+ }
+ }
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..c9d615c
--- /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 Resources$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/content/res/TypedArray_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java
new file mode 100644
index 0000000..0a7899a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/TypedArray_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 android.content.res;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.TypedValue;
+
+public class TypedArray_Delegate {
+
+ @LayoutlibDelegate
+ public static boolean getValueAt(TypedArray theTypedArray, int index, TypedValue outValue) {
+ // pass
+ return false;
+ }
+}
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/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
new file mode 100644
index 0000000..5256b58
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -0,0 +1,177 @@
+/*
+ * 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.impl.DelegateManager;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.resources.Density;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.res.BridgeResources.NinePatchInputStream;
+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 Bitmap nativeDecodeStream(InputStream is, byte[] storage,
+ Rect padding, Options opts) {
+ return nativeDecodeStream(is, storage, padding, opts, false, 1.f);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage,
+ Rect padding, Options opts, boolean applyScale, float scale) {
+ Bitmap bm = null;
+
+ //TODO support rescaling
+
+ 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 nativeDecodeAsset(int asset, Rect padding, Options opts,
+ boolean applyScale, float scale) {
+ 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_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
new file mode 100644
index 0000000..65a75b0
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
@@ -0,0 +1,252 @@
+/*
+ * 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;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ @Override
+ 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;
+ }
+
+
+ @Override
+ 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..4121f79
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -0,0 +1,592 @@
+/*
+ * 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.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 boolean mHasMipMap = false; // TODO: check the default.
+ 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.nativeToConfig(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;
+ }
+
+ public boolean hasMipMap() {
+ // TODO: check if more checks are required as in hasAlpha.
+ return mHasMipMap;
+ }
+ /**
+ * 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.nativeToConfig(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.nativeToConfig(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 boolean nativeHasMipMap(int nativeBitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return true;
+ }
+
+ return delegate.mHasMipMap;
+ }
+
+ @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 void nativeSetHasMipMap(int nativeBitmap, boolean hasMipMap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mHasMipMap = hasMipMap;
+ }
+
+ @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
+ // TODO: pass correct width, height, isPremultiplied
+ return new Bitmap(nativeInt, null /* buffer */, -1 /* width */, -1 /* height */, density,
+ isMutable, true /* isPremultiplied */,
+ null /*ninePatchChunk*/, null /* layoutBounds */);
+ }
+
+ /**
+ * 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_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
new file mode 100644
index 0000000..361f5d7
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -0,0 +1,1362 @@
+/*
+ * 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.geom.Arc2D;
+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() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ 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 void freeTextLayoutCaches() {
+ // nothing to be done here yet.
+ }
+
+ @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);
+ }
+
+ // 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 copyNativeCanvasState(int srcCanvas, int dstCanvas) {
+ // get the delegate from the native int.
+ Canvas_Delegate srcCanvasDelegate = sManager.getDelegate(srcCanvas);
+ if (srcCanvasDelegate == null) {
+ return;
+ }
+
+ // get the delegate from the native int.
+ Canvas_Delegate dstCanvasDelegate = sManager.getDelegate(dstCanvas);
+ if (dstCanvasDelegate == null) {
+ return;
+ }
+ // TODO: actually copy the canvas state.
+ }
+
+ @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.concatenate(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) {
+ // FIXME properly implement quickReject
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_quickReject(int nativeCanvas,
+ int path) {
+ // FIXME properly implement quickReject
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_quickReject(int nativeCanvas,
+ float left, float top,
+ float right, float bottom) {
+ // 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() {
+
+ @Override
+ 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() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ 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() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ int style = paintDelegate.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() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ int style = paintDelegate.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, cx + radius, cy + radius),
+ paint);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawArc(int nativeCanvas,
+ final RectF oval, final float startAngle, final float sweep,
+ final boolean useCenter, int paint) {
+ if (oval.right > oval.left && oval.bottom > oval.top) {
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ int style = paintDelegate.getStyle();
+
+ Arc2D.Float arc = new Arc2D.Float(
+ oval.left, oval.top, oval.width(), oval.height(),
+ -startAngle, -sweep,
+ useCenter ? Arc2D.PIE : Arc2D.OPEN);
+
+ // draw
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fill(arc);
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.draw(arc);
+ }
+ }
+ });
+ }
+ }
+
+ @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() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ int style = paintDelegate.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() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ Shape shape = pathDelegate.getJavaShape();
+ int style = paintDelegate.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() {
+ @Override
+ 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() {
+ @Override
+ 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() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ // 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 (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
+ // TODO: check the value of bidiFlags.
+ float m = paintDelegate.measureText(text, index, count, 0);
+ if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
+ x -= m / 2;
+ } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
+ x -= m;
+ }
+ }
+
+ List<FontInfo> fonts = paintDelegate.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, 0, count, 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 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() {
+ @Override
+ 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_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_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/Gradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java
new file mode 100644
index 0000000..7475c22
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java
@@ -0,0 +1,212 @@
+/*
+ * 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.graphics.Shader.TileMode;
+
+/**
+ * Base class for true Gradient shader delegate.
+ */
+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.
+ *
+ * @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.
+ */
+ protected Gradient_Delegate(int colors[], float positions[]) {
+ if (colors.length < 2) {
+ throw new IllegalArgumentException("needs >= 2 number of colors");
+ }
+ if (positions != null && colors.length != positions.length) {
+ throw new IllegalArgumentException("color and position arrays must be of equal length");
+ }
+
+ if (positions == null) {
+ float spacing = 1.f / (colors.length - 1);
+ positions = new float[colors.length];
+ positions[0] = 0.f;
+ positions[colors.length-1] = 1.f;
+ for (int i = 1; i < colors.length - 1 ; i++) {
+ positions[i] = spacing * i;
+ }
+ }
+
+ mColors = colors;
+ mPositions = positions;
+ }
+
+ /**
+ * Base class for (Java) Gradient Paints. This handles computing the gradient colors based
+ * on the color and position lists, as well as the {@link TileMode}
+ *
+ */
+ protected abstract static class GradientPaint implements java.awt.Paint {
+ private final static int GRADIENT_SIZE = 100;
+
+ private final int[] mColors;
+ private final float[] mPositions;
+ private final TileMode mTileMode;
+ private int[] mGradient;
+
+ protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
+ mColors = colors;
+ mPositions = positions;
+ mTileMode = tileMode;
+ }
+
+ @Override
+ public int getTransparency() {
+ return java.awt.Paint.TRANSLUCENT;
+ }
+
+ /**
+ * Pre-computes the colors for the gradient. This must be called once before any call
+ * to {@link #getGradientColor(float)}
+ */
+ 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
+ mGradient = new int[GRADIENT_SIZE+1];
+
+ int prevPos = 0;
+ int nextPos = 1;
+ for (int i = 0 ; i <= GRADIENT_SIZE ; i++) {
+ // compute current position
+ float currentPos = (float)i/GRADIENT_SIZE;
+ while (currentPos > mPositions[nextPos]) {
+ prevPos = nextPos++;
+ }
+
+ float percent = (currentPos - mPositions[prevPos]) /
+ (mPositions[nextPos] - mPositions[prevPos]);
+
+ mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
+ }
+ }
+ }
+
+ /**
+ * Returns the color based on the position in the gradient.
+ * <var>pos</var> can be anything, even &lt; 0 or &gt; > 1, as the gradient
+ * will use {@link TileMode} value to convert it into a [0,1] value.
+ */
+ protected int getGradientColor(float pos) {
+ if (pos < 0.f) {
+ if (mTileMode != null) {
+ switch (mTileMode) {
+ case CLAMP:
+ pos = 0.f;
+ break;
+ case REPEAT:
+ // 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
+ int intPart = (int)Math.floor(pos);
+ pos = pos - intPart;
+ // 0 -> 1 : normal order
+ // 1 -> 2: mirrored
+ // etc..
+ // this means if the intpart is odd we invert
+ if ((intPart % 2) == 1) {
+ pos = 1.f - pos;
+ }
+ break;
+ }
+ } else {
+ pos = 0.0f;
+ }
+ } else if (pos > 1f) {
+ if (mTileMode != null) {
+ switch (mTileMode) {
+ case CLAMP:
+ pos = 1.f;
+ break;
+ case REPEAT:
+ // remove the integer part to stay in the [0,1] range
+ pos = pos - (float)Math.floor(pos);
+ break;
+ case MIRROR:
+ // get the integer and the decimal part
+ int intPart = (int)Math.floor(pos);
+ pos = pos - intPart;
+ // 0 -> 1 : normal order
+ // 1 -> 2: mirrored
+ // etc..
+ // this means if the intpart is odd we invert
+ if ((intPart % 2) == 1) {
+ pos = 1.f - pos;
+ }
+ break;
+ }
+ } else {
+ pos = 1.0f;
+ }
+ }
+
+ int index = (int)((pos * GRADIENT_SIZE) + .5);
+
+ return mGradient[index];
+ }
+
+ /**
+ * Returns the color between c1, and c2, based on the percent of the distance
+ * between c1 and c2.
+ */
+ private int computeColor(int c1, int c2, float percent) {
+ int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
+ int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
+ int g = computeChannel((c1 >> 8) & 0xFF, (c2 >> 8) & 0xFF, percent);
+ int b = computeChannel((c1 ) & 0xFF, (c2 ) & 0xFF, percent);
+ return a << 24 | r << 16 | g << 8 | b;
+ }
+
+ /**
+ * Returns the channel value between 2 values based on the percent of the distance between
+ * the 2 values..
+ */
+ 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_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
new file mode 100644
index 0000000..f117fca
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
@@ -0,0 +1,240 @@
+/*
+ * 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;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ @Override
+ 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_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
new file mode 100644
index 0000000..5df2a21
--- /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 (other == 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 (other == 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..be27b54
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -0,0 +1,226 @@
+/*
+ * 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() {
+ @Override
+ 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/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..c9c9800
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -0,0 +1,1284 @@
+/*
+ * 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;
+import java.util.Locale;
+
+/**
+ * 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 int mHintingMode = Paint.HINTING_ON;
+
+ private Xfermode_Delegate mXfermode;
+ private ColorFilter_Delegate mColorFilter;
+ private Shader_Delegate mShader;
+ private PathEffect_Delegate mPathEffect;
+ private MaskFilter_Delegate mMaskFilter;
+ private Rasterizer_Delegate mRasterizer;
+
+ private Locale mLocale = Locale.getDefault();
+
+
+ // ---- 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 int getHinting(Paint thisPaint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return Paint.HINTING_ON;
+ }
+
+ return delegate.mHintingMode;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setHinting(Paint thisPaint, int mode) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mHintingMode = mode;
+ }
+
+ @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, int bidiFlags) {
+ // get the delegate
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.measureText(text, index, count, bidiFlags);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float native_measureText(Paint thisPaint, String text, int start, int end,
+ int bidiFlags) {
+ return native_measureText(thisPaint, text.toCharArray(), start, end - start, bidiFlags);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float native_measureText(Paint thisPaint, String text, int bidiFlags) {
+ return native_measureText(thisPaint, text.toCharArray(), 0, text.length(), bidiFlags);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_breakText(Paint thisPaint, char[] text, int index, int count,
+ float maxWidth, int bidiFlags, 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, bidiFlags);
+
+ 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, int bidiFlags, float[] measuredWidth) {
+ return native_breakText(thisPaint, text.toCharArray(), 0, text.length(), maxWidth,
+ bidiFlags, 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 void native_setTextLocale(int native_object, String locale) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.setTextLocale(locale);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getTextWidths(int native_object, char[] text, int index,
+ int count, int bidiFlags, 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, int bidiFlags, float[] widths) {
+ return native_getTextWidths(native_object, text.toCharArray(), start, end - start,
+ bidiFlags, widths);
+ }
+
+ @LayoutlibDelegate
+ /* package */static int native_getTextGlyphs(int native_object, String text, int start,
+ int end, int contextStart, int contextEnd, int flags, char[] glyphs) {
+ // FIXME
+ return 0;
+ }
+
+ @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, int bidiFlags, Rect bounds) {
+ nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start, bidiFlags,
+ bounds);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeGetCharArrayBounds(int nativePaint, char[] text, int index,
+ int count, int bidiFlags, 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;
+ mHintingMode = paint.mHintingMode;
+ 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();
+ mHintingMode = Paint.HINTING_ON;
+ }
+
+ /**
+ * 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, 1, 0, 0));
+ }
+ info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);
+
+ infoList.add(info);
+ }
+
+ mFonts = Collections.unmodifiableList(infoList);
+ }
+ }
+
+ /*package*/ float measureText(char[] text, int index, int count, int bidiFlags) {
+ // TODO: find out what bidiFlags actually does.
+
+ // 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 void setTextLocale(String locale) {
+ mLocale = new Locale(locale);
+ }
+
+ 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/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..64f19d3
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -0,0 +1,815 @@
+/*
+ * 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.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+/**
+ * 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) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.mPath.append(new Ellipse2D.Float(
+ oval.left, oval.top, oval.width(), oval.height()), false);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addCircle(int nPath, float x, float y, float radius, int dir) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ // because x/y is the center of the circle, need to offset this by the radius
+ pathDelegate.mPath.append(new Ellipse2D.Float(
+ x - radius, y - radius, radius * 2, radius * 2), false);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addArc(int nPath, RectF oval,
+ float startAngle, float sweepAngle) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ // because x/y is the center of the circle, need to offset this by the radius
+ pathDelegate.mPath.append(new Arc2D.Float(
+ oval.left, oval.top, oval.width(), oval.height(),
+ -startAngle, -sweepAngle, Arc2D.OPEN), false);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addRoundRect(
+ int nPath, RectF rect, float rx, float ry, int dir) {
+
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.mPath.append(new RoundRectangle2D.Float(
+ rect.left, rect.top, rect.width(), rect.height(), rx * 2, ry * 2), false);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addRoundRect(int nPath, RectF rect, float[] radii, int dir) {
+ // Java2D doesn't support different rounded corners in each corner, so just use the
+ // first value.
+ native_addRoundRect(nPath, rect, radii[0], radii[1], dir);
+
+ // there can be a case where this API is used but with similar values for all corners, so
+ // in that case we don't warn.
+ // we only care if 2 corners are different so just compare to the next one.
+ for (int i = 0 ; i < 3 ; i++) {
+ if (radii[i * 2] != radii[(i + 1) * 2] || radii[i * 2 + 1] != radii[(i + 1) * 2 + 1]) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Different corner sizes are not supported in Path.addRoundRect.",
+ null, null /*data*/);
+ break;
+ }
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addPath(int nPath, int src, float dx, float dy) {
+ addPath(nPath, src, AffineTransform.getTranslateInstance(dx, dy));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addPath(int nPath, int src) {
+ addPath(nPath, src, null /*transform*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addPath(int nPath, int src, int matrix) {
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
+ if (matrixDelegate == null) {
+ return;
+ }
+
+ addPath(nPath, src, matrixDelegate.getAffineTransform());
+ }
+
+ @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;
+ }
+
+ private static void addPath(int destPath, int srcPath, AffineTransform transform) {
+ Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
+ if (destPathDelegate == null) {
+ return;
+ }
+
+ Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
+ if (srcPathDelegate == null) {
+ return;
+ }
+
+ if (transform != null) {
+ destPathDelegate.mPath.append(
+ srcPathDelegate.mPath.getPathIterator(transform), false);
+ } else {
+ destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
+ }
+ }
+
+
+ /**
+ * 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_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
new file mode 100644
index 0000000..3fe45fa
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
@@ -0,0 +1,215 @@
+/*
+ * 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;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ @Override
+ 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_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_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java
new file mode 100644
index 0000000..13ae12e
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java
@@ -0,0 +1,222 @@
+/*
+ * 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;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ @Override
+ 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_Accessor.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
new file mode 100644
index 0000000..adad2ac
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class Typeface_Accessor {
+
+ public static void resetDefaults() {
+ Typeface.sDefaults = null;
+ }
+}
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..8701cc8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_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.layoutlib.bridge.impl.FontLoader;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.res.AssetManager;
+
+import java.awt.Font;
+import java.io.File;
+import java.util.ArrayList;
+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 {
+
+ private static final String SYSTEM_FONTS = "/system/fonts/";
+
+ // ---- 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 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) {
+ if (path.startsWith(SYSTEM_FONTS) ) {
+ String relativePath = path.substring(SYSTEM_FONTS.length());
+ File f = new File(sFontLoader.getOsFontsLocation(), relativePath);
+
+ try {
+ Font font = Font.createFont(Font.TRUETYPE_FONT, f);
+ if (font != null) {
+ Typeface_Delegate newDelegate = new Typeface_Delegate(font);
+ return sManager.addNewDelegate(newDelegate);
+ }
+ } catch (Exception e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
+ String.format("Unable to load font %1$s", relativePath),
+ null /*throwable*/, null /*data*/);
+ }
+ } else {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Typeface.createFromFile() can only work with platform fonts located in " +
+ SYSTEM_FONTS,
+ null /*throwable*/, null /*data*/);
+ }
+
+
+ // return a copy of the base font
+ return nativeCreate(null, 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;
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ private Typeface_Delegate(String family, int style) {
+ mFamily = family;
+ mStyle = style;
+ }
+
+ private Typeface_Delegate(Font font) {
+ mFamily = font.getFamily();
+ mStyle = Typeface.NORMAL;
+
+ mFonts = sFontLoader.getFallbackFonts(mStyle);
+
+ // insert the font glyph first.
+ mFonts.add(0, font);
+ }
+
+ private void init() {
+ mFonts = sFontLoader.getFont(mFamily, mStyle);
+ }
+}
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/HandlerThread_Delegate.java b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java
new file mode 100644
index 0000000..afbe97c
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java
@@ -0,0 +1,80 @@
+/*
+ * 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.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Delegate overriding selected methods of android.os.HandlerThread
+ *
+ * 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 HandlerThread_Delegate {
+
+ private static Map<BridgeContext, List<HandlerThread>> sThreads =
+ new HashMap<BridgeContext, List<HandlerThread>>();
+
+ public static void cleanUp(BridgeContext context) {
+ List<HandlerThread> list = sThreads.get(context);
+ if (list != null) {
+ for (HandlerThread thread : list) {
+ thread.quit();
+ }
+
+ list.clear();
+ sThreads.remove(context);
+ }
+ }
+
+ // -------- Delegate methods
+
+ @LayoutlibDelegate
+ /*package*/ static void run(HandlerThread theThread) {
+ // record the thread so that it can be quit() on clean up.
+ BridgeContext context = RenderAction.getCurrentContext();
+ List<HandlerThread> list = sThreads.get(context);
+ if (list == null) {
+ list = new ArrayList<HandlerThread>();
+ sThreads.put(context, list);
+ }
+
+ list.add(theThread);
+
+ // ---- START DEFAULT IMPLEMENTATION.
+
+ theThread.mTid = Process.myTid();
+ Looper.prepare();
+ synchronized (theThread) {
+ theThread.mLooper = Looper.myLooper();
+ theThread.notifyAll();
+ }
+ Process.setThreadPriority(theThread.mPriority);
+ theThread.onLooperPrepared();
+ Looper.loop();
+ theThread.mTid = -1;
+ }
+}
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/Looper_Accessor.java b/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java
new file mode 100644
index 0000000..09f3e47
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/Looper_Accessor.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 android.os;
+
+import java.lang.reflect.Field;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class Looper_Accessor {
+
+ public static void cleanupThread() {
+ // clean up the looper
+ Looper.sThreadLocal.remove();
+ try {
+ Field sMainLooper = Looper.class.getDeclaredField("sMainLooper");
+ sMainLooper.setAccessible(true);
+ sMainLooper.set(null, null);
+ } catch (SecurityException e) {
+ catchReflectionException();
+ } catch (IllegalArgumentException e) {
+ catchReflectionException();
+ } catch (NoSuchFieldException e) {
+ catchReflectionException();
+ } catch (IllegalAccessException e) {
+ catchReflectionException();
+ }
+
+ }
+
+ private static void catchReflectionException() {
+ assert(false);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/os/ServiceManager.java b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
new file mode 100644
index 0000000..6a68ee2
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
@@ -0,0 +1,72 @@
+/*
+ * 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 android.os;
+
+import java.util.Map;
+
+public final class ServiceManager {
+
+ /**
+ * Returns a reference to a service with the given name.
+ *
+ * @param name the name of the service to get
+ * @return a reference to the service, or <code>null</code> if the service doesn't exist
+ */
+ public static IBinder getService(String name) {
+ return null;
+ }
+
+ /**
+ * Place a new @a service called @a name into the service
+ * manager.
+ *
+ * @param name the name of the new service
+ * @param service the service object
+ */
+ public static void addService(String name, IBinder service) {
+ // pass
+ }
+
+ /**
+ * Retrieve an existing service called @a name from the
+ * service manager. Non-blocking.
+ */
+ public static IBinder checkService(String name) {
+ return null;
+ }
+
+ /**
+ * Return a list of all currently running services.
+ */
+ public static String[] listServices() throws RemoteException {
+ // actual implementation returns null sometimes, so it's ok
+ // to return null instead of an empty list.
+ return null;
+ }
+
+ /**
+ * This is only intended to be called when the process is first being brought
+ * up and bound by the activity manager. There is only one thread in the process
+ * at that time, so no locking is done.
+ *
+ * @param cache the cache of service references
+ * @hide
+ */
+ public static void initServiceCache(Map<String, IBinder> cache) {
+ // pass
+ }
+}
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..fd594f7
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
@@ -0,0 +1,106 @@
+/*
+ * 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();
+ private static long sBootTimeNano = System.nanoTime();
+
+ @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 nanoseconds since boot, including time spent in sleep.
+ *
+ * @return elapsed nanoseconds since boot.
+ */
+ @LayoutlibDelegate
+ /*package*/ static long elapsedRealtimeNanos() {
+ return System.nanoTime() - sBootTimeNano;
+ }
+
+ /**
+ * Returns milliseconds running in the current thread.
+ *
+ * @return elapsed milliseconds in the thread
+ */
+ @LayoutlibDelegate
+ /*package*/ static long currentThreadTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ /**
+ * Returns microseconds running in the current thread.
+ *
+ * @return elapsed microseconds in the thread
+ *
+ * @hide
+ */
+ @LayoutlibDelegate
+ /*package*/ static long currentThreadTimeMicro() {
+ return System.currentTimeMillis() * 1000;
+ }
+
+ /**
+ * Returns current wall time in microseconds.
+ *
+ * @return elapsed microseconds in wall time
+ *
+ * @hide
+ */
+ @LayoutlibDelegate
+ /*package*/ static long currentTimeMicro() {
+ return elapsedRealtime() * 1000;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
new file mode 100644
index 0000000..52b8f34
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
@@ -0,0 +1,37 @@
+/*
+ * 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.text;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+
+/**
+ * Delegate used to provide new implementation for the native methods of {@link AndroidBidi}
+ *
+ * Through the layoutlib_create tool, the original methods of AndroidBidi have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class AndroidBidi_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo) {
+ // return the equivalent of Layout.DIR_LEFT_TO_RIGHT
+ // TODO: actually figure the direction.
+ return 0;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java
new file mode 100644
index 0000000..8cd1a69
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.text.format;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+
+
+/**
+ * Delegate used to provide new implementation for the native methods of {@link DateFormat}
+ *
+ * Through the layoutlib_create tool, the original methods of DateFormat have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class DateFormat_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static boolean is24HourFormat(Context context) {
+ return false;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
new file mode 100644
index 0000000..6ac5b02
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
@@ -0,0 +1,283 @@
+/*
+ * 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.util;
+
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.internal.util.XmlUtils;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.resources.ResourceType;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A correct implementation of the {@link AttributeSet} interface on top of a XmlPullParser
+ */
+public class BridgeXmlPullAttributes extends XmlPullAttributes {
+
+ private final BridgeContext mContext;
+ private final boolean mPlatformFile;
+
+ public BridgeXmlPullAttributes(XmlPullParser parser, BridgeContext context,
+ boolean platformFile) {
+ super(parser);
+ mContext = context;
+ mPlatformFile = platformFile;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see android.util.XmlPullAttributes#getAttributeNameResource(int)
+ *
+ * This methods must return com.android.internal.R.attr.<name> matching
+ * the name of the attribute.
+ * It returns 0 if it doesn't find anything.
+ */
+ @Override
+ public int getAttributeNameResource(int index) {
+ // get the attribute name.
+ String name = getAttributeName(index);
+
+ // get the attribute namespace
+ String ns = mParser.getAttributeNamespace(index);
+
+ if (BridgeConstants.NS_RESOURCES.equals(ns)) {
+ Integer v = Bridge.getResourceId(ResourceType.ATTR, name);
+ if (v != null) {
+ return v.intValue();
+ }
+
+ return 0;
+ }
+
+ // 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().getResourceId(ResourceType.ATTR, name);
+ if (v != null) {
+ return v.intValue();
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public int getAttributeListValue(String namespace, String attribute,
+ String[] options, int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToList(value, options, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(String namespace, String attribute,
+ boolean defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToBoolean(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+
+ return resolveResourceValue(value, defaultValue);
+ }
+
+ @Override
+ public int getAttributeIntValue(String namespace, String attribute,
+ int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(String namespace, String attribute,
+ int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public float getAttributeFloatValue(String namespace, String attribute,
+ float defaultValue) {
+ String s = getAttributeValue(namespace, attribute);
+ if (s != null) {
+ ResourceValue r = getResourceValue(s);
+
+ if (r != null) {
+ s = r.getValue();
+ }
+
+ return Float.parseFloat(s);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeListValue(int index,
+ String[] options, int defaultValue) {
+ return XmlUtils.convertValueToList(
+ getAttributeValue(index), options, defaultValue);
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+ String value = getAttributeValue(index);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToBoolean(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeResourceValue(int index, int defaultValue) {
+ String value = getAttributeValue(index);
+
+ return resolveResourceValue(value, defaultValue);
+ }
+
+ @Override
+ public int getAttributeIntValue(int index, int defaultValue) {
+ String value = getAttributeValue(index);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+ String value = getAttributeValue(index);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public float getAttributeFloatValue(int index, float defaultValue) {
+ String s = getAttributeValue(index);
+ if (s != null) {
+ ResourceValue r = getResourceValue(s);
+
+ if (r != null) {
+ s = r.getValue();
+ }
+
+ return Float.parseFloat(s);
+ }
+
+ return defaultValue;
+ }
+
+ // -- private helper methods
+
+ /**
+ * Returns a resolved {@link ResourceValue} from a given value.
+ */
+ private ResourceValue getResourceValue(String value) {
+ // now look for this particular value
+ RenderResources resources = mContext.getRenderResources();
+ return resources.resolveResValue(resources.findResValue(value, mPlatformFile));
+ }
+
+ /**
+ * Resolves and return a value to its associated integer.
+ */
+ private int resolveResourceValue(String value, int defaultValue) {
+ ResourceValue resource = getResourceValue(value);
+ if (resource != null) {
+ Integer id = null;
+ if (mPlatformFile || resource.isFramework()) {
+ id = Bridge.getResourceId(resource.getResourceType(), resource.getName());
+ } else {
+ id = mContext.getProjectCallback().getResourceId(
+ resource.getResourceType(), resource.getName());
+ }
+
+ if (id != null) {
+ return id;
+ }
+ }
+
+ return defaultValue;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java b/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java
new file mode 100644
index 0000000..8b4c60b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2007 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.util;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * 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.
+ *
+ */
+/*package*/ final class FloatMath_Delegate {
+
+ /** Prevents instantiation. */
+ private FloatMath_Delegate() {}
+
+ /**
+ * Returns the float conversion of the most positive (i.e. closest to
+ * positive infinity) integer value which is less than the argument.
+ *
+ * @param value to be converted
+ * @return the floor of value
+ */
+ @LayoutlibDelegate
+ /*package*/ static float floor(float value) {
+ return (float)Math.floor(value);
+ }
+
+ /**
+ * Returns the float conversion of the most negative (i.e. closest to
+ * negative infinity) integer value which is greater than the argument.
+ *
+ * @param value to be converted
+ * @return the ceiling of value
+ */
+ @LayoutlibDelegate
+ /*package*/ static float ceil(float value) {
+ return (float)Math.ceil(value);
+ }
+
+ /**
+ * Returns the closest float approximation of the sine of the argument.
+ *
+ * @param angle to compute the cosine of, in radians
+ * @return the sine of angle
+ */
+ @LayoutlibDelegate
+ /*package*/ static float sin(float angle) {
+ return (float)Math.sin(angle);
+ }
+
+ /**
+ * Returns the closest float approximation of the cosine of the argument.
+ *
+ * @param angle to compute the cosine of, in radians
+ * @return the cosine of angle
+ */
+ @LayoutlibDelegate
+ /*package*/ static float cos(float angle) {
+ return (float)Math.cos(angle);
+ }
+
+ /**
+ * Returns the closest float approximation of the square root of the
+ * argument.
+ *
+ * @param value to compute sqrt of
+ * @return the square root of value
+ */
+ @LayoutlibDelegate
+ /*package*/ static float sqrt(float value) {
+ return (float)Math.sqrt(value);
+ }
+
+ /**
+ * Returns the closest float approximation of the raising "e" to the power
+ * of the argument.
+ *
+ * @param value to compute the exponential of
+ * @return the exponential of value
+ */
+ @LayoutlibDelegate
+ /*package*/ static float exp(float value) {
+ return (float)Math.exp(value);
+ }
+
+ /**
+ * Returns the closest float approximation of the result of raising {@code
+ * x} to the power of {@code y}.
+ *
+ * @param x the base of the operation.
+ * @param y the exponent of the operation.
+ * @return {@code x} to the power of {@code y}.
+ */
+ @LayoutlibDelegate
+ /*package*/ static float pow(float x, float y) {
+ return (float)Math.pow(x, y);
+ }
+
+ /**
+ * Returns {@code sqrt(}<i>{@code x}</i><sup>{@code 2}</sup>{@code +} <i>
+ * {@code y}</i><sup>{@code 2}</sup>{@code )}.
+ *
+ * @param x a float number
+ * @param y a float number
+ * @return the hypotenuse
+ */
+ @LayoutlibDelegate
+ /*package*/ static float hypot(float x, float y) {
+ return (float)Math.sqrt(x*x + y*y);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/util/Log_Delegate.java b/tools/layoutlib/bridge/src/android/util/Log_Delegate.java
new file mode 100644
index 0000000..7f432ab
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/Log_Delegate.java
@@ -0,0 +1,51 @@
+/*
+ * 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.util;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+class Log_Delegate {
+ // to replicate prefix visible when using 'adb logcat'
+ private static char priorityChar(int priority) {
+ switch (priority) {
+ case Log.VERBOSE:
+ return 'V';
+ case Log.DEBUG:
+ return 'D';
+ case Log.INFO:
+ return 'I';
+ case Log.WARN:
+ return 'W';
+ case Log.ERROR:
+ return 'E';
+ case Log.ASSERT:
+ return 'A';
+ default:
+ return '?';
+ }
+ }
+
+ @LayoutlibDelegate
+ static int println_native(int bufID, int priority, String tag, String msgs) {
+ String prefix = priorityChar(priority) + "/" + tag + ": ";
+ for (String msg: msgs.split("\n")) {
+ System.out.println(prefix + msg);
+ }
+ return 0;
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/util/LruCache.java b/tools/layoutlib/bridge/src/android/util/LruCache.java
new file mode 100644
index 0000000..5208606
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/LruCache.java
@@ -0,0 +1,391 @@
+/*
+ * 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.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * BEGIN LAYOUTLIB CHANGE
+ * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
+ * END LAYOUTLIB CHANGE
+ *
+ * A cache that holds strong references to a limited number of values. Each time
+ * a value is accessed, it is moved to the head of a queue. When a value is
+ * added to a full cache, the value at the end of that queue is evicted and may
+ * become eligible for garbage collection.
+ *
+ * <p>If your cached values hold resources that need to be explicitly released,
+ * override {@link #entryRemoved}.
+ *
+ * <p>If a cache miss should be computed on demand for the corresponding keys,
+ * override {@link #create}. This simplifies the calling code, allowing it to
+ * assume a value will always be returned, even when there's a cache miss.
+ *
+ * <p>By default, the cache size is measured in the number of entries. Override
+ * {@link #sizeOf} to size the cache in different units. For example, this cache
+ * is limited to 4MiB of bitmaps:
+ * <pre> {@code
+ * int cacheSize = 4 * 1024 * 1024; // 4MiB
+ * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
+ * protected int sizeOf(String key, Bitmap value) {
+ * return value.getByteCount();
+ * }
+ * }}</pre>
+ *
+ * <p>This class is thread-safe. Perform multiple cache operations atomically by
+ * synchronizing on the cache: <pre> {@code
+ * synchronized (cache) {
+ * if (cache.get(key) == null) {
+ * cache.put(key, value);
+ * }
+ * }}</pre>
+ *
+ * <p>This class does not allow null to be used as a key or value. A return
+ * value of null from {@link #get}, {@link #put} or {@link #remove} is
+ * unambiguous: the key was not in the cache.
+ *
+ * <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
+ * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
+ * Support Package</a> for earlier releases.
+ */
+public class LruCache<K, V> {
+ private final LinkedHashMap<K, V> map;
+
+ /** Size of this cache in units. Not necessarily the number of elements. */
+ private int size;
+ private int maxSize;
+
+ private int putCount;
+ private int createCount;
+ private int evictionCount;
+ private int hitCount;
+ private int missCount;
+
+ /**
+ * @param maxSize for caches that do not override {@link #sizeOf}, this is
+ * the maximum number of entries in the cache. For all other caches,
+ * this is the maximum sum of the sizes of the entries in this cache.
+ */
+ public LruCache(int maxSize) {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+ this.maxSize = maxSize;
+ this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
+ }
+
+ /**
+ * Sets the size of the cache.
+ * @param maxSize The new maximum size.
+ *
+ * @hide
+ */
+ public void resize(int maxSize) {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+
+ synchronized (this) {
+ this.maxSize = maxSize;
+ }
+ trimToSize(maxSize);
+ }
+
+ /**
+ * Returns the value for {@code key} if it exists in the cache or can be
+ * created by {@code #create}. If a value was returned, it is moved to the
+ * head of the queue. This returns null if a value is not cached and cannot
+ * be created.
+ */
+ public final V get(K key) {
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+
+ V mapValue;
+ synchronized (this) {
+ mapValue = map.get(key);
+ if (mapValue != null) {
+ hitCount++;
+ return mapValue;
+ }
+ missCount++;
+ }
+
+ /*
+ * Attempt to create a value. This may take a long time, and the map
+ * may be different when create() returns. If a conflicting value was
+ * added to the map while create() was working, we leave that value in
+ * the map and release the created value.
+ */
+
+ V createdValue = create(key);
+ if (createdValue == null) {
+ return null;
+ }
+
+ synchronized (this) {
+ createCount++;
+ mapValue = map.put(key, createdValue);
+
+ if (mapValue != null) {
+ // There was a conflict so undo that last put
+ map.put(key, mapValue);
+ } else {
+ size += safeSizeOf(key, createdValue);
+ }
+ }
+
+ if (mapValue != null) {
+ entryRemoved(false, key, createdValue, mapValue);
+ return mapValue;
+ } else {
+ trimToSize(maxSize);
+ return createdValue;
+ }
+ }
+
+ /**
+ * Caches {@code value} for {@code key}. The value is moved to the head of
+ * the queue.
+ *
+ * @return the previous value mapped by {@code key}.
+ */
+ public final V put(K key, V value) {
+ if (key == null || value == null) {
+ throw new NullPointerException("key == null || value == null");
+ }
+
+ V previous;
+ synchronized (this) {
+ putCount++;
+ size += safeSizeOf(key, value);
+ previous = map.put(key, value);
+ if (previous != null) {
+ size -= safeSizeOf(key, previous);
+ }
+ }
+
+ if (previous != null) {
+ entryRemoved(false, key, previous, value);
+ }
+
+ trimToSize(maxSize);
+ return previous;
+ }
+
+ /**
+ * @param maxSize the maximum size of the cache before returning. May be -1
+ * to evict even 0-sized elements.
+ */
+ private void trimToSize(int maxSize) {
+ while (true) {
+ K key;
+ V value;
+ synchronized (this) {
+ if (size < 0 || (map.isEmpty() && size != 0)) {
+ throw new IllegalStateException(getClass().getName()
+ + ".sizeOf() is reporting inconsistent results!");
+ }
+
+ if (size <= maxSize) {
+ break;
+ }
+
+ // BEGIN LAYOUTLIB CHANGE
+ // get the last item in the linked list.
+ // This is not efficient, the goal here is to minimize the changes
+ // compared to the platform version.
+ Map.Entry<K, V> toEvict = null;
+ for (Map.Entry<K, V> entry : map.entrySet()) {
+ toEvict = entry;
+ }
+ // END LAYOUTLIB CHANGE
+
+ if (toEvict == null) {
+ break;
+ }
+
+ key = toEvict.getKey();
+ value = toEvict.getValue();
+ map.remove(key);
+ size -= safeSizeOf(key, value);
+ evictionCount++;
+ }
+
+ entryRemoved(true, key, value, null);
+ }
+ }
+
+ /**
+ * Removes the entry for {@code key} if it exists.
+ *
+ * @return the previous value mapped by {@code key}.
+ */
+ public final V remove(K key) {
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+
+ V previous;
+ synchronized (this) {
+ previous = map.remove(key);
+ if (previous != null) {
+ size -= safeSizeOf(key, previous);
+ }
+ }
+
+ if (previous != null) {
+ entryRemoved(false, key, previous, null);
+ }
+
+ return previous;
+ }
+
+ /**
+ * Called for entries that have been evicted or removed. This method is
+ * invoked when a value is evicted to make space, removed by a call to
+ * {@link #remove}, or replaced by a call to {@link #put}. The default
+ * implementation does nothing.
+ *
+ * <p>The method is called without synchronization: other threads may
+ * access the cache while this method is executing.
+ *
+ * @param evicted true if the entry is being removed to make space, false
+ * if the removal was caused by a {@link #put} or {@link #remove}.
+ * @param newValue the new value for {@code key}, if it exists. If non-null,
+ * this removal was caused by a {@link #put}. Otherwise it was caused by
+ * an eviction or a {@link #remove}.
+ */
+ protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
+
+ /**
+ * Called after a cache miss to compute a value for the corresponding key.
+ * Returns the computed value or null if no value can be computed. The
+ * default implementation returns null.
+ *
+ * <p>The method is called without synchronization: other threads may
+ * access the cache while this method is executing.
+ *
+ * <p>If a value for {@code key} exists in the cache when this method
+ * returns, the created value will be released with {@link #entryRemoved}
+ * and discarded. This can occur when multiple threads request the same key
+ * at the same time (causing multiple values to be created), or when one
+ * thread calls {@link #put} while another is creating a value for the same
+ * key.
+ */
+ protected V create(K key) {
+ return null;
+ }
+
+ private int safeSizeOf(K key, V value) {
+ int result = sizeOf(key, value);
+ if (result < 0) {
+ throw new IllegalStateException("Negative size: " + key + "=" + value);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the size of the entry for {@code key} and {@code value} in
+ * user-defined units. The default implementation returns 1 so that size
+ * is the number of entries and max size is the maximum number of entries.
+ *
+ * <p>An entry's size must not change while it is in the cache.
+ */
+ protected int sizeOf(K key, V value) {
+ return 1;
+ }
+
+ /**
+ * Clear the cache, calling {@link #entryRemoved} on each removed entry.
+ */
+ public final void evictAll() {
+ trimToSize(-1); // -1 will evict 0-sized elements
+ }
+
+ /**
+ * For caches that do not override {@link #sizeOf}, this returns the number
+ * of entries in the cache. For all other caches, this returns the sum of
+ * the sizes of the entries in this cache.
+ */
+ public synchronized final int size() {
+ return size;
+ }
+
+ /**
+ * For caches that do not override {@link #sizeOf}, this returns the maximum
+ * number of entries in the cache. For all other caches, this returns the
+ * maximum sum of the sizes of the entries in this cache.
+ */
+ public synchronized final int maxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Returns the number of times {@link #get} returned a value that was
+ * already present in the cache.
+ */
+ public synchronized final int hitCount() {
+ return hitCount;
+ }
+
+ /**
+ * Returns the number of times {@link #get} returned null or required a new
+ * value to be created.
+ */
+ public synchronized final int missCount() {
+ return missCount;
+ }
+
+ /**
+ * Returns the number of times {@link #create(Object)} returned a value.
+ */
+ public synchronized final int createCount() {
+ return createCount;
+ }
+
+ /**
+ * Returns the number of times {@link #put} was called.
+ */
+ public synchronized final int putCount() {
+ return putCount;
+ }
+
+ /**
+ * Returns the number of values that have been evicted.
+ */
+ public synchronized final int evictionCount() {
+ return evictionCount;
+ }
+
+ /**
+ * Returns a copy of the current contents of the cache, ordered from least
+ * recently accessed to most recently accessed.
+ */
+ public synchronized final Map<K, V> snapshot() {
+ return new LinkedHashMap<K, V>(map);
+ }
+
+ @Override public synchronized final String toString() {
+ int accesses = hitCount + missCount;
+ int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
+ return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
+ maxSize, hitCount, missCount, hitPercent);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
new file mode 100644
index 0000000..4901f72
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.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.view;
+
+import com.android.layoutlib.bridge.android.BridgeWindow;
+import com.android.layoutlib.bridge.android.BridgeWindowSession;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.View.AttachInfo;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class AttachInfo_Accessor {
+
+ public static void setAttachInfo(View view) {
+ Context context = view.getContext();
+ WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ ViewRootImpl root = new ViewRootImpl(context, display);
+ AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(),
+ display, root, new Handler(), null);
+ info.mHasWindowFocus = true;
+ info.mWindowVisibility = View.VISIBLE;
+ info.mInTouchMode = false; // this is so that we can display selections.
+ info.mHardwareAccelerated = false;
+ view.dispatchAttachedToWindow(info, 0);
+ }
+
+ public static void dispatchOnPreDraw(View view) {
+ view.mAttachInfo.mTreeObserver.dispatchOnPreDraw();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
new file mode 100644
index 0000000..941f1ce6
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -0,0 +1,272 @@
+/*
+ * 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.view;
+
+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.ResourceReference;
+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.layoutlib.bridge.impl.ParserFactory;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+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;
+
+/**
+ * Custom implementation of {@link LayoutInflater} to handle custom views.
+ */
+public final class BridgeInflater extends LayoutInflater {
+
+ private final IProjectCallback mProjectCallback;
+ private boolean mIsInMerge = false;
+ private ResourceReference mResourceReference;
+
+ /**
+ * 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."
+ };
+
+ protected BridgeInflater(LayoutInflater original, Context newContext) {
+ 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.
+ */
+ public BridgeInflater(Context context, IProjectCallback projectCallback) {
+ super(context);
+ mProjectCallback = projectCallback;
+ mConstructorArgs[0] = context;
+ }
+
+ @Override
+ public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ View view = null;
+
+ try {
+ // First try to find a class using the default Android prefixes
+ for (String prefix : sClassPrefixList) {
+ try {
+ view = createView(name, prefix, attrs);
+ if (view != null) {
+ break;
+ }
+ } catch (ClassNotFoundException e) {
+ // 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 {
+ if (view == null) {
+ view = super.onCreateView(name, attrs);
+ }
+ } 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) {
+ view = loadCustomView(name, attrs);
+ }
+ } catch (ClassNotFoundException e) {
+ // If the class was not found, we throw the exception directly, because this
+ // method is already expected to throw it.
+ throw e;
+ }
+ } catch (Exception e) {
+ // Wrap the real exception in a ClassNotFoundException, so that the calling method
+ // can deal with it.
+ ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e);
+ throw exception;
+ }
+
+ setupViewInContext(view, attrs);
+
+ return view;
+ }
+
+ @Override
+ public View createViewFromTag(View parent, String name, AttributeSet attrs) {
+ View view = null;
+ try {
+ view = super.createViewFromTag(parent, name, attrs);
+ } catch (InflateException e) {
+ // try to load the class from using the custom view loader
+ try {
+ view = loadCustomView(name, attrs);
+ } catch (Exception e2) {
+ // 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) {
+ exception.initCause(e2);
+ } else {
+ exception.initCause(e);
+ }
+ 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;
+
+ ResourceValue value = null;
+
+ Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
+ if (layoutInfo != null) {
+ value = bridgeContext.getRenderResources().getFrameworkResource(
+ ResourceType.LAYOUT, layoutInfo.getSecond());
+ } else {
+ layoutInfo = mProjectCallback.resolveResourceId(resource);
+
+ if (layoutInfo != null) {
+ value = bridgeContext.getRenderResources().getProjectResource(
+ ResourceType.LAYOUT, layoutInfo.getSecond());
+ }
+ }
+
+ if (value != null) {
+ File f = new File(value.getValue());
+ if (f.isFile()) {
+ try {
+ XmlPullParser parser = ParserFactory.create(f);
+
+ BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
+ parser, bridgeContext, false);
+
+ return inflate(bridgeParser, root);
+ } catch (Exception e) {
+ 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) {
+ // first get the classname in case it's not the node name
+ 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;
+ }
+ }
+
+ return null;
+ }
+
+ private void setupViewInContext(View view, AttributeSet attrs) {
+ if (getContext() instanceof BridgeContext) {
+ BridgeContext bc = (BridgeContext) getContext();
+ if (attrs instanceof BridgeXmlBlockParser) {
+ BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs;
+
+ // get the view key
+ Object viewKey = parser.getViewCookie();
+
+ if (viewKey == null) {
+ int currentDepth = parser.getDepth();
+
+ // test whether we are in an included file or in a adapter binding view.
+ BridgeXmlBlockParser previousParser = bc.getPreviousParser();
+ if (previousParser != null) {
+ // looks like we inside an embedded layout.
+ // only apply the cookie of the calling node (<include>) if we are at the
+ // top level of the embedded layout. If there is a merge tag, then
+ // skip it and look for the 2nd level
+ int testDepth = mIsInMerge ? 2 : 1;
+ if (currentDepth == testDepth) {
+ viewKey = previousParser.getViewCookie();
+ // if we are in a merge, wrap the cookie in a MergeCookie.
+ if (viewKey != null && mIsInMerge) {
+ viewKey = new MergeCookie(viewKey);
+ }
+ }
+ } else if (mResourceReference != null && currentDepth == 1) {
+ // else if there's a resource reference, this means we are in an adapter
+ // binding case. Set the resource ref as the view cookie only for the top
+ // level view.
+ viewKey = mResourceReference;
+ }
+ }
+
+ if (viewKey != null) {
+ bc.addViewKey(view, viewKey);
+ }
+ }
+ }
+ }
+
+ public void setIsInMerge(boolean isInMerge) {
+ mIsInMerge = isInMerge;
+ }
+
+ public void setResourceReference(ResourceReference reference) {
+ mResourceReference = reference;
+ }
+
+ @Override
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new BridgeInflater(this, newContext);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
new file mode 100644
index 0000000..f75ee50
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 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.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Choreographer}
+ *
+ * Through the layoutlib_create tool, the original methods of Choreographer have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class Choreographer_Delegate {
+
+ @LayoutlibDelegate
+ public static float getRefreshRate() {
+ return 60.f;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/Display_Delegate.java b/tools/layoutlib/bridge/src/android/view/Display_Delegate.java
new file mode 100644
index 0000000..53dc821
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/Display_Delegate.java
@@ -0,0 +1,36 @@
+/*
+ * 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.tools.layoutlib.annotations.LayoutlibDelegate;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Display}
+ *
+ * Through the layoutlib_create tool, the original methods of Display have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class Display_Delegate {
+
+ @LayoutlibDelegate
+ static void updateDisplayInfoLocked(Display theDisplay) {
+ // do nothing
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
new file mode 100644
index 0000000..f0c3a75
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -0,0 +1,502 @@
+/*
+ * 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 android.graphics.Point;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.IApplicationToken;
+import android.view.IInputFilter;
+import android.view.IOnKeyguardExitResult;
+import android.view.IRotationWatcher;
+import android.view.IWindowManager;
+import android.view.IWindowSession;
+
+import java.util.List;
+
+/**
+ * Basic implementation of {@link IWindowManager} so that {@link Display} (and
+ * {@link Display_Delegate}) can return a valid instance.
+ */
+public class IWindowManagerImpl implements IWindowManager {
+
+ private final Configuration mConfig;
+ private final DisplayMetrics mMetrics;
+ private final int mRotation;
+ private final boolean mHasNavigationBar;
+
+ public IWindowManagerImpl(Configuration config, DisplayMetrics metrics, int rotation,
+ boolean hasNavigationBar) {
+ mConfig = config;
+ mMetrics = metrics;
+ mRotation = rotation;
+ mHasNavigationBar = hasNavigationBar;
+ }
+
+ // custom API.
+
+ public DisplayMetrics getMetrics() {
+ return mMetrics;
+ }
+
+ // ---- implementation of IWindowManager that we care about ----
+
+ @Override
+ public int getRotation() throws RemoteException {
+ return mRotation;
+ }
+
+ @Override
+ public boolean hasNavigationBar() {
+ return mHasNavigationBar;
+ }
+
+ // ---- unused implementation of IWindowManager ----
+
+ @Override
+ public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4,
+ boolean arg5, boolean arg6, int arg7)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void addWindowToken(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void clearForcedDisplaySize(int displayId) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void clearForcedDisplayDensity(int displayId) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setOverscan(int displayId, int left, int top, int right, int bottom)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void closeSystemDialogs(String arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void startFreezingScreen(int exitAnim, int enterAnim) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void stopFreezingScreen() {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void disableKeyguard(IBinder arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void executeAppTransition() throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void exitKeyguardSecurely(IOnKeyguardExitResult arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void freezeRotation(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public float getAnimationScale(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public float[] getAnimationScales() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getAppOrientation(IApplicationToken arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public int getPendingAppTransition() throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public boolean inKeyguardRestrictedInputMode() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean inputMethodClientHasFocus(IInputMethodClient arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardLocked() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardSecure() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isViewServerRunning() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public IWindowSession openSession(IInputMethodClient arg0, IInputContext arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void overridePendingAppTransition(String arg0, int arg1, int arg2,
+ IRemoteCallback startedCallback) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
+ int startHeight) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY,
+ IRemoteCallback startedCallback, boolean scaleUp) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void pauseKeyDispatching(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void prepareAppTransition(int arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void reenableKeyguard(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void removeAppToken(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void removeWindowToken(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void resumeKeyDispatching(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Bitmap screenshotApplications(IBinder arg0, int displayId, int arg1, int arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setAnimationScale(int arg0, float arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAnimationScales(float[] arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAppGroupId(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAppOrientation(IApplicationToken arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setAppStartingWindow(IBinder arg0, String arg1, int arg2, CompatibilityInfo arg3,
+ CharSequence arg4, int arg5, int arg6, int arg7, int arg8, IBinder arg9, boolean arg10)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setAppVisibility(IBinder arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAppWillBeHidden(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setEventDispatching(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setFocusedApp(IBinder arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void getInitialDisplaySize(int displayId, Point size) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void getBaseDisplaySize(int displayId, Point size) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setForcedDisplaySize(int displayId, int arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public int getInitialDisplayDensity(int displayId) {
+ return -1;
+ }
+
+ @Override
+ public int getBaseDisplayDensity(int displayId) {
+ return -1;
+ }
+
+ @Override
+ public void setForcedDisplayDensity(int displayId, int density) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setInTouchMode(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setNewConfiguration(Configuration arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void updateRotation(boolean arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setStrictModeVisualIndicatorPreference(String arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void showStrictModeViolation(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void startAppFreezingScreen(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean startViewServer(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void statusBarVisibilityChanged(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void stopAppFreezingScreen(IBinder arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean stopViewServer() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void thawRotation() throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int watchRotation(IRotationWatcher arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public void removeRotationWatcher(IRotationWatcher arg0) throws RemoteException {
+ }
+
+ @Override
+ public boolean waitForWindowDrawn(IBinder token, IRemoteCallback callback) {
+ return false;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getPreferredOptionsPanelGravity() throws RemoteException {
+ return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ }
+
+ @Override
+ public void dismissKeyguard() {
+ }
+
+ @Override
+ public void lockNow(Bundle options) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean isSafeModeEnabled() {
+ return false;
+ }
+
+ @Override
+ public void showAssistant() {
+
+ }
+
+ @Override
+ public IBinder getFocusedWindowToken() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setInputFilter(IInputFilter filter) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void getWindowFrame(IBinder token, Rect outFrame) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setMagnificationCallbacks(IMagnificationCallbacks callbacks) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setMagnificationSpec(MagnificationSpec spec) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean isRotationFrozen() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+}
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..3db3a1b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
@@ -0,0 +1,199 @@
+/*
+ * 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.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+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 {
+
+ private static final String TAG_MERGE = "merge";
+
+ public static boolean sIsInInclude = false;
+
+ /**
+ * Recursive method used to descend down the xml hierarchy and instantiate
+ * views, instantiate their children, and then call onFinishInflate().
+ *
+ * This implementation just records the merge status before calling the default implementation.
+ */
+ @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.
+
+ thisInflater.rInflate_Original(parser, parent, attrs, finishInflate);
+
+ // ---- END DEFAULT IMPLEMENTATION.
+
+ if (finishInflate == false) {
+ // this is a merge rInflate!
+ if (thisInflater instanceof BridgeInflater) {
+ ((BridgeInflater) thisInflater).setIsInMerge(false);
+ }
+ }
+ }
+
+ @LayoutlibDelegate
+ public static void parseInclude(
+ LayoutInflater thisInflater,
+ XmlPullParser parser, View parent, AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+
+ int type;
+
+ if (parent instanceof ViewGroup) {
+ final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
+ if (layout == 0) {
+ final String value = attrs.getAttributeValue(null, "layout");
+ if (value == null) {
+ throw new InflateException("You must specifiy a layout in the"
+ + " include tag: <include layout=\"@layout/layoutID\" />");
+ } else {
+ throw new InflateException("You must specifiy a valid layout "
+ + "reference. The layout ID " + value + " is not valid.");
+ }
+ } else {
+ final XmlResourceParser childParser =
+ thisInflater.getContext().getResources().getLayout(layout);
+
+ try {
+ final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
+
+ while ((type = childParser.next()) != XmlPullParser.START_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ // Empty.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new InflateException(childParser.getPositionDescription() +
+ ": No start tag found!");
+ }
+
+ final String childName = childParser.getName();
+
+ if (TAG_MERGE.equals(childName)) {
+ // Inflate all children.
+ thisInflater.rInflate(childParser, parent, childAttrs, false);
+ } else {
+ final View view = thisInflater.createViewFromTag(parent, childName, childAttrs);
+ final ViewGroup group = (ViewGroup) parent;
+
+ // We try to load the layout params set in the <include /> tag. If
+ // they don't exist, we will rely on the layout params set in the
+ // included XML file.
+ // During a layoutparams generation, a runtime exception is thrown
+ // if either layout_width or layout_height is missing. We catch
+ // this exception and set localParams accordingly: true means we
+ // successfully loaded layout params from the <include /> tag,
+ // false means we need to rely on the included layout params.
+ ViewGroup.LayoutParams params = null;
+ try {
+ // ---- START CHANGES
+ sIsInInclude = true;
+ // ---- END CHANGES
+
+ params = group.generateLayoutParams(attrs);
+
+ } catch (RuntimeException e) {
+ // ---- START CHANGES
+ sIsInInclude = false;
+ // ---- END CHANGES
+
+ params = group.generateLayoutParams(childAttrs);
+ } finally {
+ // ---- START CHANGES
+ sIsInInclude = false;
+ // ---- END CHANGES
+
+ if (params != null) {
+ view.setLayoutParams(params);
+ }
+ }
+
+ // Inflate all children.
+ thisInflater.rInflate(childParser, view, childAttrs, true);
+
+ // Attempt to override the included layout's android:id with the
+ // one set on the <include /> tag itself.
+ TypedArray a = thisInflater.mContext.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.View, 0, 0);
+ int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
+ // While we're at it, let's try to override android:visibility.
+ int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
+ a.recycle();
+
+ if (id != View.NO_ID) {
+ view.setId(id);
+ }
+
+ switch (visibility) {
+ case 0:
+ view.setVisibility(View.VISIBLE);
+ break;
+ case 1:
+ view.setVisibility(View.INVISIBLE);
+ break;
+ case 2:
+ view.setVisibility(View.GONE);
+ break;
+ }
+
+ group.addView(view);
+ }
+ } finally {
+ childParser.close();
+ }
+ }
+ } else {
+ throw new InflateException("<include /> can only be used inside of a ViewGroup");
+ }
+
+ final int currentDepth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
+ // Empty
+ }
+ }
+
+
+}
diff --git a/tools/layoutlib/bridge/src/android/view/SurfaceView.java b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
new file mode 100644
index 0000000..6aa4b3b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
@@ -0,0 +1,112 @@
+/*
+ * 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.view;
+
+import com.android.layoutlib.bridge.MockView;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+/**
+ * Mock version of the SurfaceView.
+ * Only non override public methods from the real SurfaceView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ *
+ * TODO: generate automatically.
+ *
+ */
+public class SurfaceView extends MockView {
+
+ public SurfaceView(Context context) {
+ this(context, null);
+ }
+
+ public SurfaceView(Context context, AttributeSet attrs) {
+ this(context, attrs , 0);
+ }
+
+ public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public SurfaceHolder getHolder() {
+ return mSurfaceHolder;
+ }
+
+ private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+
+ @Override
+ public boolean isCreating() {
+ return false;
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ }
+
+ @Override
+ public void setFixedSize(int width, int height) {
+ }
+
+ @Override
+ public void setSizeFromLayout() {
+ }
+
+ @Override
+ public void setFormat(int format) {
+ }
+
+ @Override
+ public void setType(int type) {
+ }
+
+ @Override
+ public void setKeepScreenOn(boolean screenOn) {
+ }
+
+ @Override
+ public Canvas lockCanvas() {
+ return null;
+ }
+
+ @Override
+ public Canvas lockCanvas(Rect dirty) {
+ return null;
+ }
+
+ @Override
+ public void unlockCanvasAndPost(Canvas canvas) {
+ }
+
+ @Override
+ public Surface getSurface() {
+ return null;
+ }
+
+ @Override
+ public Rect getSurfaceFrame() {
+ return null;
+ }
+ };
+}
+
diff --git a/tools/layoutlib/bridge/src/android/view/ViewConfiguration_Accessor.java b/tools/layoutlib/bridge/src/android/view/ViewConfiguration_Accessor.java
new file mode 100644
index 0000000..c3533e0
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ViewConfiguration_Accessor.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class ViewConfiguration_Accessor {
+
+ public static void clearConfigurations() {
+ // clear the stored ViewConfiguration since the map is per density and not per context.
+ ViewConfiguration.sConfigurations.clear();
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java
new file mode 100644
index 0000000..14b84ef
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 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.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link ViewRootImpl}
+ *
+ * Through the layoutlib_create tool, the original methods of ViewRootImpl have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class ViewRootImpl_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isInTouchMode() {
+ return false; // this allows displaying selection.
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/View_Delegate.java b/tools/layoutlib/bridge/src/android/view/View_Delegate.java
new file mode 100644
index 0000000..8215f7c
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/View_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 android.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * 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 {
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isInEditMode(View thisView) {
+ return true;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/WindowManagerGlobal_Delegate.java b/tools/layoutlib/bridge/src/android/view/WindowManagerGlobal_Delegate.java
new file mode 100644
index 0000000..2606e55
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/WindowManagerGlobal_Delegate.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of
+ * {@link WindowManagerGlobal}
+ *
+ * Through the layoutlib_create tool, the original methods of WindowManagerGlobal have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class WindowManagerGlobal_Delegate {
+
+ private static IWindowManager sService;
+
+ @LayoutlibDelegate
+ public static IWindowManager getWindowManagerService() {
+ return sService;
+ }
+
+ // ---- internal implementation stuff ----
+
+ public static void setWindowManagerService(IWindowManager service) {
+ sService = service;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java
new file mode 100644
index 0000000..1fd7836
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java
@@ -0,0 +1,137 @@
+/*
+ * 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 android.view.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.view.IWindow;
+import android.view.View;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
+ * Such events are generated when something notable happens in the user interface,
+ * for example an {@link android.app.Activity} starts, the focus or selection of a
+ * {@link android.view.View} changes etc. Parties interested in handling accessibility
+ * events implement and register an accessibility service which extends
+ * {@link android.accessibilityservice.AccessibilityService}.
+ *
+ * @see AccessibilityEvent
+ * @see android.accessibilityservice.AccessibilityService
+ * @see android.content.Context#getSystemService
+ */
+public final class AccessibilityManager {
+ private static AccessibilityManager sInstance = new AccessibilityManager();
+
+ /**
+ * Listener for the accessibility state.
+ */
+ public interface AccessibilityStateChangeListener {
+
+ /**
+ * Called back on change in the accessibility state.
+ *
+ * @param enabled Whether accessibility is enabled.
+ */
+ public void onAccessibilityStateChanged(boolean enabled);
+ }
+
+ /**
+ * Get an AccessibilityManager instance (create one if necessary).
+ *
+ * @hide
+ */
+ public static AccessibilityManager getInstance(Context context) {
+ return sInstance;
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param context A {@link Context}.
+ */
+ private AccessibilityManager() {
+ }
+
+ /**
+ * Returns if the {@link AccessibilityManager} is enabled.
+ *
+ * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ return false;
+ }
+
+ /**
+ * Sends an {@link AccessibilityEvent}. If this {@link AccessibilityManager} is not
+ * enabled the call is a NOOP.
+ *
+ * @param event The {@link AccessibilityEvent}.
+ *
+ * @throws IllegalStateException if a client tries to send an {@link AccessibilityEvent}
+ * while accessibility is not enabled.
+ */
+ public void sendAccessibilityEvent(AccessibilityEvent event) {
+ }
+
+ /**
+ * Requests interruption of the accessibility feedback from all accessibility services.
+ */
+ public void interrupt() {
+ }
+
+ /**
+ * Returns the {@link ServiceInfo}s of the installed accessibility services.
+ *
+ * @return An unmodifiable list with {@link ServiceInfo}s.
+ */
+ public List<ServiceInfo> getAccessibilityServiceList() {
+ // normal implementation does this in some case, so let's do the same
+ // (unmodifiableList wrapped around null).
+ List<ServiceInfo> services = null;
+ return Collections.unmodifiableList(services);
+ }
+
+ public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
+ // normal implementation does this in some case, so let's do the same
+ // (unmodifiableList wrapped around null).
+ List<AccessibilityServiceInfo> services = null;
+ return Collections.unmodifiableList(services);
+ }
+
+ public boolean addAccessibilityStateChangeListener(
+ AccessibilityStateChangeListener listener) {
+ return true;
+ }
+
+ public boolean removeAccessibilityStateChangeListener(
+ AccessibilityStateChangeListener listener) {
+ return true;
+ }
+
+ public int addAccessibilityInteractionConnection(IWindow windowToken,
+ IAccessibilityInteractionConnection connection) {
+ return View.NO_ID;
+ }
+
+ public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
new file mode 100644
index 0000000..dc4f9c8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
@@ -0,0 +1,27 @@
+/*
+ * 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.inputmethod;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class InputMethodManager_Accessor {
+
+ public static void resetInstance() {
+ InputMethodManager.sInstance = null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
new file mode 100644
index 0000000..7c98847
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
@@ -0,0 +1,49 @@
+/*
+ * 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.inputmethod;
+
+import com.android.layoutlib.bridge.android.BridgeIInputMethodManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.os.Looper;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link InputMethodManager}
+ *
+ * Through the layoutlib_create tool, the original methods of InputMethodManager have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class InputMethodManager_Delegate {
+
+ // ---- Overridden methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static InputMethodManager getInstance() {
+ synchronized (InputMethodManager.class) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm == null) {
+ imm = new InputMethodManager(
+ new BridgeIInputMethodManager(), Looper.getMainLooper());
+ InputMethodManager.sInstance = imm;
+ }
+ return imm;
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/webkit/WebView.java b/tools/layoutlib/bridge/src/android/webkit/WebView.java
new file mode 100644
index 0000000..3b66188
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/webkit/WebView.java
@@ -0,0 +1,254 @@
+/*
+ * 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.webkit;
+
+import com.android.layoutlib.bridge.MockView;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Picture;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Mock version of the WebView.
+ * Only non override public methods from the real WebView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ *
+ * TODO: generate automatically.
+ *
+ */
+public class WebView extends MockView {
+
+ /**
+ * Construct a new WebView with a Context object.
+ * @param context A Context object used to access application assets.
+ */
+ public WebView(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Construct a new WebView with layout parameters.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ */
+ public WebView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.webViewStyle);
+ }
+
+ /**
+ * Construct a new WebView with layout parameters and a default style.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ * @param defStyle The default style resource ID.
+ */
+ public WebView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ // START FAKE PUBLIC METHODS
+
+ public void setHorizontalScrollbarOverlay(boolean overlay) {
+ }
+
+ public void setVerticalScrollbarOverlay(boolean overlay) {
+ }
+
+ public boolean overlayHorizontalScrollbar() {
+ return false;
+ }
+
+ public boolean overlayVerticalScrollbar() {
+ return false;
+ }
+
+ public void savePassword(String host, String username, String password) {
+ }
+
+ public void setHttpAuthUsernamePassword(String host, String realm,
+ String username, String password) {
+ }
+
+ public String[] getHttpAuthUsernamePassword(String host, String realm) {
+ return null;
+ }
+
+ public void destroy() {
+ }
+
+ public static void enablePlatformNotifications() {
+ }
+
+ public static void disablePlatformNotifications() {
+ }
+
+ public WebBackForwardList saveState(Bundle outState) {
+ return null;
+ }
+
+ public WebBackForwardList restoreState(Bundle inState) {
+ return null;
+ }
+
+ public void loadUrl(String url) {
+ }
+
+ public void loadData(String data, String mimeType, String encoding) {
+ }
+
+ public void loadDataWithBaseURL(String baseUrl, String data,
+ String mimeType, String encoding, String failUrl) {
+ }
+
+ public void stopLoading() {
+ }
+
+ public void reload() {
+ }
+
+ public boolean canGoBack() {
+ return false;
+ }
+
+ public void goBack() {
+ }
+
+ public boolean canGoForward() {
+ return false;
+ }
+
+ public void goForward() {
+ }
+
+ public boolean canGoBackOrForward(int steps) {
+ return false;
+ }
+
+ public void goBackOrForward(int steps) {
+ }
+
+ public boolean pageUp(boolean top) {
+ return false;
+ }
+
+ public boolean pageDown(boolean bottom) {
+ return false;
+ }
+
+ public void clearView() {
+ }
+
+ public Picture capturePicture() {
+ return null;
+ }
+
+ public float getScale() {
+ return 0;
+ }
+
+ public void setInitialScale(int scaleInPercent) {
+ }
+
+ public void invokeZoomPicker() {
+ }
+
+ public void requestFocusNodeHref(Message hrefMsg) {
+ }
+
+ public void requestImageRef(Message msg) {
+ }
+
+ public String getUrl() {
+ return null;
+ }
+
+ public String getTitle() {
+ return null;
+ }
+
+ public Bitmap getFavicon() {
+ return null;
+ }
+
+ public int getProgress() {
+ return 0;
+ }
+
+ public int getContentHeight() {
+ return 0;
+ }
+
+ public void pauseTimers() {
+ }
+
+ public void resumeTimers() {
+ }
+
+ public void clearCache() {
+ }
+
+ public void clearFormData() {
+ }
+
+ public void clearHistory() {
+ }
+
+ public void clearSslPreferences() {
+ }
+
+ public WebBackForwardList copyBackForwardList() {
+ return null;
+ }
+
+ public static String findAddress(String addr) {
+ return null;
+ }
+
+ public void documentHasImages(Message response) {
+ }
+
+ public void setWebViewClient(WebViewClient client) {
+ }
+
+ public void setDownloadListener(DownloadListener listener) {
+ }
+
+ public void setWebChromeClient(WebChromeClient client) {
+ }
+
+ public void addJavascriptInterface(Object obj, String interfaceName) {
+ }
+
+ public WebSettings getSettings() {
+ return null;
+ }
+
+ public View getZoomControls() {
+ return null;
+ }
+
+ public boolean zoomIn() {
+ return false;
+ }
+
+ public boolean zoomOut() {
+ return false;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java b/tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java
new file mode 100644
index 0000000..0100dc5
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012 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.policy;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.RenderAction;
+
+import android.content.Context;
+import android.view.BridgeInflater;
+import android.view.FallbackEventHandler;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManagerPolicy;
+
+/**
+ * Custom implementation of PolicyManager that does nothing to run in LayoutLib.
+ *
+ */
+public class PolicyManager {
+
+ public static Window makeNewWindow(Context context) {
+ // this will likely crash somewhere beyond so we log it.
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "Call to PolicyManager.makeNewWindow is not supported", null);
+ return null;
+ }
+
+ public static LayoutInflater makeNewLayoutInflater(Context context) {
+ return new BridgeInflater(context, RenderAction.getCurrentContext().getProjectCallback());
+ }
+
+ public static WindowManagerPolicy makeNewWindowManager() {
+ // this will likely crash somewhere beyond so we log it.
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "Call to PolicyManager.makeNewWindowManager is not supported", null);
+ return null;
+ }
+
+ public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
+ return new FallbackEventHandler() {
+ @Override
+ public void setView(View v) {
+ }
+
+ @Override
+ public void preDispatchKeyEvent(KeyEvent event) {
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return false;
+ }
+ };
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java
new file mode 100644
index 0000000..3017292
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java
@@ -0,0 +1,111 @@
+/*
+ * 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.textservice;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.SpellCheckerSubtype;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of
+ * {@link ITextServicesManager$Stub}
+ *
+ * Through the layoutlib_create tool, the original methods of Stub have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class ITextServicesManager_Stub_Delegate {
+
+ @LayoutlibDelegate
+ public static ITextServicesManager asInterface(IBinder obj) {
+ // ignore the obj and return a fake interface implementation
+ return new FakeTextServicesManager();
+ }
+
+ private static class FakeTextServicesManager implements ITextServicesManager {
+
+ @Override
+ public void finishSpellCheckerService(ISpellCheckerSessionListener arg0)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void getSpellCheckerService(String arg0, String arg1,
+ ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean isSpellCheckerEnabled() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setSpellCheckerEnabled(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ }
+ }
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
new file mode 100644
index 0000000..42257c5
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -0,0 +1,621 @@
+/*
+ * 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 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.Result.Status;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layoutlib.bridge.impl.FontLoader;
+import com.android.layoutlib.bridge.impl.RenderDrawable;
+import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.layoutlib.bridge.util.DynamicIdMap;
+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.BridgeAssetManager;
+import android.graphics.Bitmap;
+import android.graphics.Typeface_Accessor;
+import android.graphics.Typeface_Delegate;
+import android.os.Looper;
+import android.os.Looper_Accessor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import java.io.File;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+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 #createScene(SceneParams)}
+ */
+public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
+
+ public static class StaticMethodNotImplementedException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public StaticMethodNotImplementedException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Lock to ensure only one rendering/inflating happens at a time.
+ * This is due to some singleton in the Android framework.
+ */
+ private final static ReentrantLock sLock = new ReentrantLock();
+
+ /**
+ * Maps from id to resource type/name. This is for com.android.internal.R
+ */
+ 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<IntArray, String> sRArrayMap = new HashMap<IntArray, String>();
+ /**
+ * Reverse map compared to sRMap, resource type -> (resource name -> id).
+ * This is for com.android.internal.R.
+ */
+ private final static Map<ResourceType, Map<String, Integer>> sRevRMap =
+ new EnumMap<ResourceType, Map<String,Integer>>(ResourceType.class);
+
+ // framework resources are defined as 0x01XX#### where XX is the resource type (layout,
+ // drawable, etc...). Using FF as the type allows for 255 resource types before we get a
+ // collision which should be fine.
+ private final static int DYNAMIC_ID_SEED_START = 0x01ff0000;
+ private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
+
+ 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<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<NinePatchChunk>> sFramework9PatchCache =
+ new HashMap<String, SoftReference<NinePatchChunk>>();
+
+ private static Map<String, Map<String, Integer>> sEnumValueMap;
+ private static Map<String, String> sPlatformProperties;
+
+ /**
+ * int[] wrapper to use as keys in maps.
+ */
+ private final static class IntArray {
+ private int[] mArray;
+
+ private IntArray() {
+ // do nothing
+ }
+
+ 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);
+ }
+
+ @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);
+ }
+ };
+
+ /**
+ * Current log.
+ */
+ private static LayoutLog sCurrentLog = sDefaultLog;
+
+ private EnumSet<Capability> mCapabilities;
+
+ @Override
+ public int getApiLevel() {
+ return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
+ }
+
+ @Override
+ public EnumSet<Capability> getCapabilities() {
+ return mCapabilities;
+ }
+
+ @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,
+ Capability.ADAPTER_BINDING,
+ Capability.EXTENDED_VIEWINFO,
+ Capability.FIXED_SCALABLE_NINE_PATCH);
+
+
+ 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
+ // throws an exception.
+ // This is useful when testing the rendering in ADT to identify static native
+ // methods that are ignored -- layoutlib_create makes them returns 0/false/null
+ // which is generally OK yet might be a problem, so this is how you'd find out.
+ //
+ // Currently layoutlib_create only overrides static native method.
+ // Static non-natives are not overridden and thus do not get here.
+ final String debug = System.getenv("DEBUG_LAYOUT");
+ if (debug != null && !debug.equals("0") && !debug.equals("false")) {
+
+ OverrideMethod.setDefaultListener(new MethodAdapter() {
+ @Override
+ public void onInvokeV(String signature, boolean isNative, Object caller) {
+ sDefaultLog.error(null, "Missing Stub: " + signature +
+ (isNative ? " (native)" : ""), null /*data*/);
+
+ if (debug.equalsIgnoreCase("throw")) {
+ // Throwing this exception doesn't seem that useful. It breaks
+ // the layout editor yet doesn't display anything meaningful to the
+ // user. Having the error in the console is just as useful. We'll
+ // throw it only if the environment variable is "throw" or "THROW".
+ throw new StaticMethodNotImplementedException(signature);
+ }
+ }
+ });
+ }
+
+ // load the fonts.
+ FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath());
+ if (fontLoader != null) {
+ Typeface_Delegate.init(fontLoader);
+ } else {
+ log.error(LayoutLog.TAG_BROKEN,
+ "Failed create FontLoader in layout lib.", null);
+ return false;
+ }
+
+ // 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 {
+ Class<?> r = com.android.internal.R.class;
+
+ for (Class<?> inner : r.getDeclaredClasses()) {
+ String resTypeName = inner.getSimpleName();
+ ResourceType resType = ResourceType.getEnum(resTypeName);
+ if (resType != null) {
+ Map<String, Integer> fullMap = new HashMap<String, Integer>();
+ sRevRMap.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 (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;
+ }
+
+ @Override
+ public boolean dispose() {
+ BridgeAssetManager.clearSystem();
+
+ // dispose of the default typeface.
+ Typeface_Accessor.resetDefaults();
+
+ return true;
+ }
+
+ /**
+ * 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
+ */
+ @Override
+ public RenderSession createSession(SessionParams params) {
+ try {
+ 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();
+ }
+
+ 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));
+ }
+ }
+
+ @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();
+ }
+
+ return lastResult;
+ } catch (Throwable t) {
+ // get the real cause of the exception.
+ Throwable t2 = t;
+ while (t2.getCause() != null) {
+ t2 = t.getCause();
+ }
+ return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
+ }
+ }
+
+ @Override
+ public void clearCaches(Object projectKey) {
+ if (projectKey != null) {
+ sProjectBitmapCache.remove(projectKey);
+ sProject9PatchCache.remove(projectKey);
+ }
+ }
+
+ @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");
+ }
+
+ /**
+ * Returns the lock for the bridge
+ */
+ public static ReentrantLock getLock() {
+ return sLock;
+ }
+
+ /**
+ * 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 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.prepareMainLooper();
+ }
+ }
+
+ /**
+ * 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 void cleanupThread() {
+ // clean up the looper
+ Looper_Accessor.cleanupThread();
+ }
+
+ public static LayoutLog getLog() {
+ return sCurrentLog;
+ }
+
+ 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)");
+ }
+
+ if (log != null) {
+ sCurrentLog = log;
+ } else {
+ sCurrentLog = sDefaultLog;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public static Pair<ResourceType, String> resolveResourceId(int value) {
+ Pair<ResourceType, String> pair = sRMap.get(value);
+ if (pair == null) {
+ pair = sDynamicIds.resolveId(value);
+ if (pair == null) {
+ //System.out.println(String.format("Missing id: %1$08X (%1$d)", value));
+ }
+ }
+ return pair;
+ }
+
+ /**
+ * Returns the name of a framework resource whose value is an int array.
+ * @param array
+ */
+ public static String resolveResourceId(int[] array) {
+ sIntArrayWrapper.set(array);
+ return sRArrayMap.get(sIntArrayWrapper);
+ }
+
+ /**
+ * 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.
+ */
+ public static Integer getResourceId(ResourceType type, String name) {
+ Map<String, Integer> map = sRevRMap.get(type);
+ Integer value = null;
+ if (map != null) {
+ value = map.get(name);
+ }
+
+ if (value == null) {
+ value = sDynamicIds.getId(type, name);
+ }
+
+ return value;
+ }
+
+ /**
+ * Returns the list of possible enums for a given attribute name.
+ */
+ public static Map<String, Integer> getEnumValues(String attributeName) {
+ if (sEnumValueMap != null) {
+ return sEnumValueMap.get(attributeName);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the platform build properties.
+ */
+ public static Map<String, String> getPlatformProperties() {
+ return sPlatformProperties;
+ }
+
+ /**
+ * Returns the bitmap for a specific path, from a specific project cache, or from the
+ * framework cache.
+ * @param value the path of the bitmap
+ * @param projectKey the key of the project, or null to query the framework cache.
+ * @return the cached Bitmap or null if not found.
+ */
+ public static Bitmap getCachedBitmap(String value, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
+ if (map != null) {
+ SoftReference<Bitmap> ref = map.get(value);
+ if (ref != null) {
+ return ref.get();
+ }
+ }
+ } else {
+ SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
+ if (ref != null) {
+ return ref.get();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets a bitmap in a project cache or in the framework cache.
+ * @param value the path of the bitmap
+ * @param bmp the Bitmap object
+ * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
+ */
+ public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
+
+ if (map == null) {
+ map = new HashMap<String, SoftReference<Bitmap>>();
+ sProjectBitmapCache.put(projectKey, map);
+ }
+
+ map.put(value, new SoftReference<Bitmap>(bmp));
+ } else {
+ sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp));
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
+
+ if (map != null) {
+ SoftReference<NinePatchChunk> ref = map.get(value);
+ if (ref != null) {
+ return ref.get();
+ }
+ }
+ } else {
+ SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
+ if (ref != null) {
+ return ref.get();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * 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.
+ */
+ public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
+
+ if (map == null) {
+ map = new HashMap<String, SoftReference<NinePatchChunk>>();
+ sProject9PatchCache.put(projectKey, map);
+ }
+
+ map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
+ } else {
+ sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java
new file mode 100644
index 0000000..eb9e7f1
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * Constant definition class.<br>
+ * <br>
+ * Most constants have a prefix defining the content.
+ * <ul>
+ * <li><code>WS_</code> Workspace path constant. Those are absolute paths,
+ * from the project root.</li>
+ * <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
+ * <li><code>FN_</code> File name constant.</li>
+ * <li><code>FD_</code> Folder name constant.</li>
+ * <li><code>EXT_</code> File extension constant. This does NOT include a dot.</li>
+ * <li><code>DOT_</code> File extension constant. This start with a dot.</li>
+ * <li><code>RE_</code> Regexp constant.</li>
+ * <li><code>NS_</code> Namespace constant.</li>
+ * <li><code>CLASS_</code> Fully qualified class name.</li>
+ * </ul>
+ *
+ */
+public class BridgeConstants {
+
+ /** Namespace for the resource XML */
+ public final static String NS_RESOURCES = "http://schemas.android.com/apk/res/android";
+
+ /** App auto namespace */
+ public final static String NS_APP_RES_AUTO = "http://schemas.android.com/apk/res-auto";
+
+ public final static String R = "com.android.internal.R";
+
+
+ public final static String MATCH_PARENT = "match_parent";
+ public final static String FILL_PARENT = "fill_parent";
+ public final static String WRAP_CONTENT = "wrap_content";
+}
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..f9f4b3a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -0,0 +1,196 @@
+/*
+ * 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.layoutlib.bridge.impl.RenderSessionImpl;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+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) {
+ // pass
+ return super.getProperty(objectView, propertyName);
+ }
+
+ @Override
+ public Result setProperty(Object objectView, String propertyName, String propertyValue) {
+ // pass
+ return super.setProperty(objectView, propertyName, propertyValue);
+ }
+
+ @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/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
new file mode 100644
index 0000000..3d50b2a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -0,0 +1,47 @@
+/*
+ * 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 android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.TextView;
+
+/**
+ * Base class for mocked views.
+ *
+ * TODO: implement onDraw and draw a rectangle in a random color with the name of the class
+ * (or better the id of the view).
+ */
+public class MockView extends TextView {
+
+ public MockView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setText(this.getClass().getSimpleName());
+ setTextColor(0xFF000000);
+ setGravity(Gravity.CENTER);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ canvas.drawARGB(0xFF, 0x7F, 0x7F, 0x7F);
+
+ super.onDraw(canvas);
+ }
+}
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..688cc87
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
@@ -0,0 +1,137 @@
+/*
+ * 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.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+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 {
+ @Override
+ public ContentProviderResult[] applyBatch(String callingPackage,
+ ArrayList<ContentProviderOperation> arg0)
+ throws RemoteException, OperationApplicationException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int bulkInsert(String callingPackage, Uri arg0, ContentValues[] arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public Bundle call(String callingPackage, String arg0, String arg1, Bundle arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int delete(String callingPackage, Uri arg0, String arg1, String[] arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public String getType(Uri arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Uri insert(String callingPackage, Uri arg0, ContentValues arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(
+ String callingPackage, Uri arg0, String arg1, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(
+ String callingPackage, Uri arg0, String arg1, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Cursor query(String callingPackage, Uri arg0, String[] arg1, String arg2, String[] arg3,
+ String arg4, ICancellationSignal arg5) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int update(String callingPackage, Uri arg0, ContentValues arg1, String arg2,
+ String[] arg3) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String[] getStreamTypes(Uri arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(String callingPackage, Uri arg0, String arg1,
+ Bundle arg2, ICancellationSignal signal) throws RemoteException, FileNotFoundException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal createCancellationSignal() throws RemoteException {
+ // 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..8d259d7
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java
@@ -0,0 +1,120 @@
+/*
+ * 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;
+ }
+
+ @Override
+ protected IContentProvider acquireUnstableProvider(Context c, String name) {
+ return acquireProvider(c, name);
+ }
+
+ @Override
+ public boolean releaseUnstableProvider(IContentProvider icp) {
+ return releaseProvider(icp);
+ }
+
+ /** @hide */
+ @Override
+ public void unstableProviderDied(IContentProvider icp) {
+ }
+
+ /**
+ * 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/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
new file mode 100644
index 0000000..d63dcac
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -0,0 +1,1427 @@
+/*
+ * 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.android;
+
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+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.ResourceReference;
+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.android.view.WindowManagerImpl;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.Stack;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.BridgeResources;
+import android.content.res.BridgeTypedArray;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.BridgeInflater;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.textservice.TextServicesManager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Custom implementation of Context/Activity to handle non compiled resources.
+ */
+public final class BridgeContext extends Context {
+
+ private Resources mSystemResources;
+ private final HashMap<View, Object> mViewKeyMap = new HashMap<View, Object>();
+ private final Object mProjectKey;
+ private final DisplayMetrics mMetrics;
+ private final RenderResources mRenderResources;
+ private final Configuration mConfig;
+ private final ApplicationInfo mApplicationInfo;
+ private final IProjectCallback mProjectCallback;
+ private final WindowManager mWindowManager;
+
+ private Resources.Theme mTheme;
+
+ private final Map<Object, Map<String, String>> mDefaultPropMaps =
+ new IdentityHashMap<Object, Map<String,String>>();
+
+ // 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 mBridgeInflater;
+
+ 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 renderResources the configured resources (both framework and projects) for this
+ * render.
+ * @param projectCallback
+ * @param config the Configuration object for this render.
+ * @param targetSdkVersion the targetSdkVersion of the application.
+ */
+ public BridgeContext(Object projectKey, DisplayMetrics metrics,
+ RenderResources renderResources,
+ IProjectCallback projectCallback,
+ Configuration config,
+ int targetSdkVersion) {
+ mProjectKey = projectKey;
+ mMetrics = metrics;
+ mProjectCallback = projectCallback;
+
+ mRenderResources = renderResources;
+ mConfig = config;
+
+ mApplicationInfo = new ApplicationInfo();
+ mApplicationInfo.targetSdkVersion = targetSdkVersion;
+
+ mWindowManager = new WindowManagerImpl(mMetrics);
+ }
+
+ /**
+ * 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();
+
+ mSystemResources = BridgeResources.initSystem(
+ this,
+ assetManager,
+ mMetrics,
+ mConfig,
+ mProjectCallback);
+ mTheme = mSystemResources.newTheme();
+ }
+
+ /**
+ * Disposes the {@link Resources} singleton.
+ */
+ public void disposeResources() {
+ BridgeResources.disposeSystem();
+ }
+
+ public void setBridgeInflater(BridgeInflater inflater) {
+ mBridgeInflater = inflater;
+ }
+
+ public void addViewKey(View view, Object viewKey) {
+ mViewKeyMap.put(view, viewKey);
+ }
+
+ public Object getViewKey(View view) {
+ return mViewKeyMap.get(view);
+ }
+
+ public Object getProjectKey() {
+ return mProjectKey;
+ }
+
+ public DisplayMetrics getMetrics() {
+ return mMetrics;
+ }
+
+ public IProjectCallback getProjectCallback() {
+ return mProjectCallback;
+ }
+
+ public RenderResources getRenderResources() {
+ return mRenderResources;
+ }
+
+ public Map<String, String> getDefaultPropMap(Object key) {
+ return mDefaultPropMaps.get(key);
+ }
+
+ public Configuration getConfiguration() {
+ return mConfig;
+ }
+
+ /**
+ * Adds a parser to the stack.
+ * @param parser the parser to add.
+ */
+ public void pushParser(BridgeXmlBlockParser parser) {
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("PUSH " + parser.getParser().toString());
+ }
+ mParserStack.push(parser);
+ }
+
+ /**
+ * Removes the parser at the top of the stack
+ */
+ public void popParser() {
+ BridgeXmlBlockParser parser = mParserStack.pop();
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("POPD " + parser.getParser().toString());
+ }
+ }
+
+ /**
+ * 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);
+ boolean isFrameworkRes = true;
+ if (resourceInfo == null) {
+ resourceInfo = mProjectCallback.resolveResourceId(resid);
+ isFrameworkRes = false;
+ }
+
+ if (resourceInfo == null) {
+ return false;
+ }
+
+ ResourceValue value = mRenderResources.findItemInTheme(resourceInfo.getSecond(),
+ isFrameworkRes);
+ 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;
+ }
+
+
+ public ResourceReference resolveId(int id) {
+ // first get the String related to this id in the framework
+ Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+
+ if (resourceInfo != null) {
+ return new ResourceReference(resourceInfo.getSecond(), true);
+ }
+
+ // didn't find a match in the framework? look in the project.
+ if (mProjectCallback != null) {
+ resourceInfo = mProjectCallback.resolveResourceId(id);
+
+ if (resourceInfo != null) {
+ return new ResourceReference(resourceInfo.getSecond(), false);
+ }
+ }
+
+ return null;
+ }
+
+ public Pair<View, Boolean> inflateView(ResourceReference resource, ViewGroup parent,
+ boolean attachToRoot, boolean skipCallbackParser) {
+ boolean isPlatformLayout = resource.isFramework();
+
+ if (isPlatformLayout == false && skipCallbackParser == false) {
+ // check if the project callback can provide us with a custom parser.
+ ILayoutPullParser parser = getParser(resource);
+
+ if (parser != null) {
+ BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser,
+ this, resource.isFramework());
+ try {
+ pushParser(blockParser);
+ return Pair.of(
+ mBridgeInflater.inflate(blockParser, parent, attachToRoot),
+ true);
+ } finally {
+ popParser();
+ }
+ }
+ }
+
+ ResourceValue resValue;
+ if (resource instanceof ResourceValue) {
+ resValue = (ResourceValue) resource;
+ } else {
+ if (isPlatformLayout) {
+ resValue = mRenderResources.getFrameworkResource(ResourceType.LAYOUT,
+ resource.getName());
+ } else {
+ resValue = mRenderResources.getProjectResource(ResourceType.LAYOUT,
+ resource.getName());
+ }
+ }
+
+ if (resValue != null) {
+
+ File xml = new File(resValue.getValue());
+ if (xml.isFile()) {
+ // we need to create a pull parser around the layout XML file, and then
+ // give that to our XmlBlockParser
+ try {
+ XmlPullParser parser = ParserFactory.create(xml);
+
+ // set the resource ref to have correct view cookies
+ mBridgeInflater.setResourceReference(resource);
+
+ BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser,
+ this, resource.isFramework());
+ try {
+ pushParser(blockParser);
+ return Pair.of(
+ mBridgeInflater.inflate(blockParser, parent, attachToRoot),
+ false);
+ } finally {
+ popParser();
+ }
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + xml, e, null /*data*/);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
+ } finally {
+ mBridgeInflater.setResourceReference(null);
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ String.format("File %s is missing!", xml), null);
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ String.format("Layout %s%s does not exist.", isPlatformLayout ? "android:" : "",
+ resource.getName()), null);
+ }
+
+ return Pair.of(null, false);
+ }
+
+ @SuppressWarnings("deprecation")
+ private ILayoutPullParser getParser(ResourceReference resource) {
+ ILayoutPullParser parser;
+ if (resource instanceof ResourceValue) {
+ parser = mProjectCallback.getParser((ResourceValue) resource);
+ } else {
+ parser = mProjectCallback.getParser(resource.getName());
+ }
+ return parser;
+ }
+
+ // ------------ Context methods
+
+ @Override
+ public Resources getResources() {
+ return mSystemResources;
+ }
+
+ @Override
+ public Theme getTheme() {
+ return mTheme;
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return this.getClass().getClassLoader();
+ }
+
+ @Override
+ public Object getSystemService(String service) {
+ if (LAYOUT_INFLATER_SERVICE.equals(service)) {
+ return mBridgeInflater;
+ }
+
+ if (TEXT_SERVICES_MANAGER_SERVICE.equals(service)) {
+ // we need to return a valid service to avoid NPE
+ return TextServicesManager.getInstance();
+ }
+
+ if (WINDOW_SERVICE.equals(service)) {
+ return mWindowManager;
+ }
+
+ // needed by SearchView
+ if (INPUT_METHOD_SERVICE.equals(service)) {
+ return null;
+ }
+
+ if (POWER_SERVICE.equals(service)) {
+ return new PowerManager(this, new BridgePowerManager(), new Handler());
+ }
+
+ throw new UnsupportedOperationException("Unsupported Service: " + service);
+ }
+
+
+ @Override
+ public final TypedArray obtainStyledAttributes(int[] attrs) {
+ return createStyleBasedTypedArray(mRenderResources.getCurrentTheme(), attrs);
+ }
+
+ @Override
+ public final TypedArray obtainStyledAttributes(int resid, int[] attrs)
+ throws Resources.NotFoundException {
+ // get the StyleResourceValue based on the resId;
+ StyleResourceValue style = getStyleByDynamicId(resid);
+
+ if (style == null) {
+ throw new Resources.NotFoundException();
+ }
+
+ if (mTypedArrayCache == null) {
+ mTypedArrayCache = new HashMap<int[], Map<Integer,TypedArray>>();
+
+ Map<Integer, TypedArray> map = new HashMap<Integer, TypedArray>();
+ mTypedArrayCache.put(attrs, map);
+
+ BridgeTypedArray ta = createStyleBasedTypedArray(style, attrs);
+ map.put(resid, ta);
+
+ return ta;
+ }
+
+ // get the 2nd map
+ Map<Integer, TypedArray> map = mTypedArrayCache.get(attrs);
+ if (map == null) {
+ map = new HashMap<Integer, TypedArray>();
+ mTypedArrayCache.put(attrs, map);
+ }
+
+ // get the array from the 2nd map
+ TypedArray ta = map.get(resid);
+
+ if (ta == null) {
+ ta = createStyleBasedTypedArray(style, attrs);
+ map.put(resid, ta);
+ }
+
+ return ta;
+ }
+
+ @Override
+ public final TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs) {
+ return obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
+ @Override
+ 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
+ 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
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Parser is not a BridgeXmlBlockParser!", null /*data*/);
+ return null;
+ }
+
+ List<Pair<String, Boolean>> attributeList = searchAttrs(attrs);
+
+ BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length,
+ isPlatformFile);
+
+ // look for a custom style.
+ String customStyle = null;
+ if (set != null) {
+ customStyle = set.getAttributeValue(null /* namespace*/, "style");
+ }
+
+ StyleResourceValue customStyleValues = null;
+ if (customStyle != null) {
+ ResourceValue item = mRenderResources.findResValue(customStyle,
+ false /*forceFrameworkOnly*/);
+
+ // resolve it in case it links to something else
+ item = mRenderResources.resolveResValue(item);
+
+ if (item instanceof StyleResourceValue) {
+ customStyleValues = (StyleResourceValue)item;
+ }
+ }
+
+ // resolve the defStyleAttr value into a IStyleResourceValue
+ StyleResourceValue defStyleValues = null;
+
+ if (defStyleAttr != 0) {
+ // get the name from the int.
+ Pair<String, Boolean> defStyleAttribute = searchAttr(defStyleAttr);
+
+ if (defaultPropMap != null) {
+ String defStyleName = defStyleAttribute.getFirst();
+ if (defStyleAttribute.getSecond()) {
+ defStyleName = "android:" + defStyleName;
+ }
+ defaultPropMap.put("style", defStyleName);
+ }
+
+ // look for the style in the current theme, and its parent:
+ ResourceValue item = mRenderResources.findItemInTheme(defStyleAttribute.getFirst(),
+ defStyleAttribute.getSecond());
+
+ if (item != null) {
+ // item is a reference to a style entry. Search for it.
+ item = mRenderResources.findResValue(item.getValue(),
+ false /*forceFrameworkOnly*/);
+
+ if (item instanceof StyleResourceValue) {
+ defStyleValues = (StyleResourceValue)item;
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
+ String.format(
+ "Failed to find style '%s' in current theme",
+ defStyleAttribute.getFirst()),
+ null /*data*/);
+ }
+ } else if (defStyleRes != 0) {
+ boolean isFrameworkRes = true;
+ Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes);
+ if (value == null) {
+ value = mProjectCallback.resolveResourceId(defStyleRes);
+ isFrameworkRes = false;
+ }
+
+ 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(),
+ isFrameworkRes);
+ 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 appNamespace = mProjectCallback.getNamespace();
+
+ if (attributeList != null) {
+ for (int index = 0 ; index < attributeList.size() ; index++) {
+ Pair<String, Boolean> attribute = attributeList.get(index);
+
+ if (attribute == null) {
+ continue;
+ }
+
+ String attrName = attribute.getFirst();
+ boolean frameworkAttr = attribute.getSecond().booleanValue();
+ String value = null;
+ if (set != null) {
+ value = set.getAttributeValue(
+ frameworkAttr ? BridgeConstants.NS_RESOURCES : appNamespace,
+ attrName);
+
+ // if this is an app attribute, and the first get fails, try with the
+ // new res-auto namespace as well
+ if (frameworkAttr == false && value == null) {
+ value = set.getAttributeValue(BridgeConstants.NS_APP_RES_AUTO, attrName);
+ }
+ }
+
+ // 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) {
+ ResourceValue resValue = null;
+
+ // look for the value in the custom style first (and its parent if needed)
+ if (customStyleValues != null) {
+ resValue = mRenderResources.findItemInStyle(customStyleValues,
+ attrName, frameworkAttr);
+ }
+
+ // then look for the value in the default Style (and its parent if needed)
+ if (resValue == null && defStyleValues != null) {
+ resValue = mRenderResources.findItemInStyle(defStyleValues,
+ attrName, frameworkAttr);
+ }
+
+ // if the item is not present in the defStyle, we look in the main theme (and
+ // its parent themes)
+ if (resValue == null) {
+ resValue = mRenderResources.findItemInTheme(attrName, frameworkAttr);
+ }
+
+ // if we found a value, we make sure this doesn't reference another value.
+ // So we resolve it.
+ if (resValue != null) {
+ // put the first default value, before the resolution.
+ if (defaultPropMap != null) {
+ defaultPropMap.put(attrName, resValue.getValue());
+ }
+
+ resValue = mRenderResources.resolveResValue(resValue);
+ }
+
+ ta.bridgeSetValue(index, attrName, frameworkAttr, 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, attrName, frameworkAttr,
+ mRenderResources.resolveValue(null, attrName, value, isPlatformFile));
+ }
+ }
+ }
+
+ ta.sealArray();
+
+ return ta;
+ }
+
+ @Override
+ public Looper getMainLooper() {
+ return Looper.myLooper();
+ }
+
+
+ // ------------- private new methods
+
+ /**
+ * Creates a {@link BridgeTypedArray} by filling the values defined by the int[] with the
+ * values found in the given style.
+ * @see #obtainStyledAttributes(int, int[])
+ */
+ private BridgeTypedArray createStyleBasedTypedArray(StyleResourceValue style, int[] attrs)
+ throws Resources.NotFoundException {
+
+ List<Pair<String, Boolean>> attributes = searchAttrs(attrs);
+
+ BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length,
+ false);
+
+ // for each attribute, get its name so that we can search it in the style
+ for (int i = 0 ; i < attrs.length ; i++) {
+ Pair<String, Boolean> attribute = attributes.get(i);
+
+ if (attribute != null) {
+ // look for the value in the given style
+ ResourceValue resValue = mRenderResources.findItemInStyle(style,
+ attribute.getFirst(), attribute.getSecond());
+
+ if (resValue != null) {
+ // resolve it to make sure there are no references left.
+ ta.bridgeSetValue(i, attribute.getFirst(), attribute.getSecond(),
+ mRenderResources.resolveResValue(resValue));
+ }
+ }
+ }
+
+ ta.sealArray();
+
+ return ta;
+ }
+
+
+ /**
+ * The input int[] attrs is a list of attributes. The returns a list of information about
+ * each attributes. The information is (name, isFramework)
+ * <p/>
+ *
+ * @param attrs An attribute array reference given to obtainStyledAttributes.
+ * @return List of attribute information.
+ */
+ private List<Pair<String, Boolean>> searchAttrs(int[] attrs) {
+ List<Pair<String, Boolean>> results = new ArrayList<Pair<String, Boolean>>(attrs.length);
+
+ // for each attribute, get its name so that we can search it in the style
+ for (int i = 0 ; i < attrs.length ; i++) {
+ Pair<ResourceType, String> resolvedResource = Bridge.resolveResourceId(attrs[i]);
+ boolean isFramework = false;
+ if (resolvedResource != null) {
+ isFramework = true;
+ } else {
+ resolvedResource = mProjectCallback.resolveResourceId(attrs[i]);
+ }
+
+ if (resolvedResource != null) {
+ results.add(Pair.of(resolvedResource.getSecond(), isFramework));
+ } else {
+ results.add(null);
+ }
+ }
+
+ return results;
+ }
+
+ /**
+ * Searches for the attribute referenced by its internal id.
+ *
+ * @param attr An attribute reference given to obtainStyledAttributes such as defStyle.
+ * @return A (name, isFramework) pair describing the attribute if found. Returns null
+ * if nothing is found.
+ */
+ public Pair<String, Boolean> searchAttr(int attr) {
+ Pair<ResourceType, String> info = Bridge.resolveResourceId(attr);
+ if (info != null) {
+ return Pair.of(info.getSecond(), Boolean.TRUE);
+ }
+
+ info = mProjectCallback.resolveResourceId(attr);
+ if (info != null) {
+ return Pair.of(info.getSecond(), Boolean.FALSE);
+ }
+
+ return null;
+ }
+
+ public int getDynamicIdByStyle(StyleResourceValue resValue) {
+ if (mDynamicIdToStyleMap == null) {
+ // create the maps.
+ mDynamicIdToStyleMap = new HashMap<Integer, StyleResourceValue>();
+ mStyleToDynamicIdMap = new HashMap<StyleResourceValue, Integer>();
+ }
+
+ // look for an existing id
+ Integer id = mStyleToDynamicIdMap.get(resValue);
+
+ if (id == null) {
+ // generate a new id
+ id = Integer.valueOf(++mDynamicIdGenerator);
+
+ // and add it to the maps.
+ mDynamicIdToStyleMap.put(id, resValue);
+ mStyleToDynamicIdMap.put(resValue, id);
+ }
+
+ return id;
+ }
+
+ private StyleResourceValue getStyleByDynamicId(int i) {
+ if (mDynamicIdToStyleMap != null) {
+ return mDynamicIdToStyleMap.get(i);
+ }
+
+ return null;
+ }
+
+ public int getFrameworkResourceValue(ResourceType resType, String resName, int defValue) {
+ Integer value = Bridge.getResourceId(resType, resName);
+ if (value != null) {
+ return value.intValue();
+ }
+
+ return defValue;
+ }
+
+ public int getProjectResourceValue(ResourceType resType, String resName, int defValue) {
+ if (mProjectCallback != null) {
+ Integer value = mProjectCallback.getResourceId(resType, resName);
+ if (value != null) {
+ return value.intValue();
+ }
+ }
+
+ return defValue;
+ }
+
+ //------------ NOT OVERRIDEN --------------------
+
+ @Override
+ public boolean bindService(Intent arg0, ServiceConnection arg1, int arg2) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String arg0) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkCallingOrSelfUriPermission(Uri arg0, int arg1) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkCallingPermission(String arg0) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkCallingUriPermission(Uri arg0, int arg1) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkPermission(String arg0, int arg1, int arg2) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkUriPermission(Uri arg0, int arg1, int arg2, int arg3) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkUriPermission(Uri arg0, String arg1, String arg2, int arg3,
+ int arg4, int arg5) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public void clearWallpaper() {
+ // pass
+
+ }
+
+ @Override
+ public Context createPackageContext(String arg0, int arg1) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Context createPackageContextAsUser(String arg0, int arg1, UserHandle user) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Context createConfigurationContext(Configuration overrideConfiguration) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Context createDisplayContext(Display display) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public String[] databaseList() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public boolean deleteDatabase(String arg0) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public boolean deleteFile(String arg0) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String arg0, String arg1) {
+ // pass
+
+ }
+
+ @Override
+ public void enforceCallingOrSelfUriPermission(Uri arg0, int arg1,
+ String arg2) {
+ // pass
+
+ }
+
+ @Override
+ public void enforceCallingPermission(String arg0, String arg1) {
+ // pass
+
+ }
+
+ @Override
+ public void enforceCallingUriPermission(Uri arg0, int arg1, String arg2) {
+ // pass
+
+ }
+
+ @Override
+ public void enforcePermission(String arg0, int arg1, int arg2, String arg3) {
+ // pass
+
+ }
+
+ @Override
+ public void enforceUriPermission(Uri arg0, int arg1, int arg2, int arg3,
+ String arg4) {
+ // pass
+
+ }
+
+ @Override
+ public void enforceUriPermission(Uri arg0, String arg1, String arg2,
+ int arg3, int arg4, int arg5, String arg6) {
+ // pass
+
+ }
+
+ @Override
+ public String[] fileList() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public AssetManager getAssets() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getCacheDir() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getExternalCacheDir() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ if (mContentResolver == null) {
+ mContentResolver = new BridgeContentResolver(this);
+ }
+ return mContentResolver;
+ }
+
+ @Override
+ public File getDatabasePath(String arg0) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getDir(String arg0, int arg1) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getFileStreamPath(String arg0) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getFilesDir() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getExternalFilesDir(String type) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public String getPackageCodePath() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public String getPackageName() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public String getBasePackageName() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ return mApplicationInfo;
+ }
+
+ @Override
+ public String getPackageResourcePath() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getSharedPrefsFile(String name) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String arg0, int arg1) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Drawable getWallpaper() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumWidth() {
+ return -1;
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumHeight() {
+ return -1;
+ }
+
+ @Override
+ public void grantUriPermission(String arg0, Uri arg1, int arg2) {
+ // pass
+
+ }
+
+ @Override
+ public FileInputStream openFileInput(String arg0) throws FileNotFoundException {
+ // pass
+ return null;
+ }
+
+ @Override
+ public FileOutputStream openFileOutput(String arg0, int arg1) throws FileNotFoundException {
+ // pass
+ return null;
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1, CursorFactory arg2) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1,
+ CursorFactory arg2, DatabaseErrorHandler arg3) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Drawable peekWallpaper() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1,
+ String arg2, Handler arg3) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(BroadcastReceiver arg0, UserHandle arg0p5,
+ IntentFilter arg1, String arg2, Handler arg3) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public void removeStickyBroadcast(Intent arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void revokeUriPermission(Uri arg0, int arg1) {
+ // pass
+
+ }
+
+ @Override
+ public void sendBroadcast(Intent arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void sendBroadcast(Intent arg0, String arg1) {
+ // pass
+
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
+ // pass
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent arg0, String arg1) {
+ // pass
+
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent arg0, String arg1,
+ BroadcastReceiver arg2, Handler arg3, int arg4, String arg5,
+ Bundle arg6) {
+ // pass
+
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission, int appOp,
+ BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+ String initialData, Bundle initialExtras) {
+ // pass
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+ // pass
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission) {
+ // pass
+ }
+
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
+ // pass
+ }
+
+ @Override
+ public void sendStickyBroadcast(Intent arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void sendStickyOrderedBroadcast(Intent intent,
+ BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ // pass
+ }
+
+ @Override
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ // pass
+ }
+
+ @Override
+ public void sendStickyOrderedBroadcastAsUser(Intent intent,
+ UserHandle user, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ // pass
+ }
+
+ @Override
+ public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ // pass
+ }
+
+ @Override
+ public void setTheme(int arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void setWallpaper(Bitmap arg0) throws IOException {
+ // pass
+
+ }
+
+ @Override
+ public void setWallpaper(InputStream arg0) throws IOException {
+ // pass
+
+ }
+
+ @Override
+ public void startActivity(Intent arg0) {
+ // pass
+ }
+
+ @Override
+ public void startActivity(Intent arg0, Bundle arg1) {
+ // pass
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ throws IntentSender.SendIntentException {
+ // pass
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ Bundle options) throws IntentSender.SendIntentException {
+ // pass
+ }
+
+ @Override
+ public boolean startInstrumentation(ComponentName arg0, String arg1,
+ Bundle arg2) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public ComponentName startService(Intent arg0) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public boolean stopService(Intent arg0) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public ComponentName startServiceAsUser(Intent arg0, UserHandle arg1) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public boolean stopServiceAsUser(Intent arg0, UserHandle arg1) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public void unbindService(ServiceConnection arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver arg0) {
+ // pass
+
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ @Override
+ public void startActivities(Intent[] arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void startActivities(Intent[] arg0, Bundle arg1) {
+ // pass
+
+ }
+
+ @Override
+ public boolean isRestricted() {
+ return false;
+ }
+
+ @Override
+ public File getObbDir() {
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "OBB not supported", null);
+ return null;
+ }
+
+ @Override
+ public DisplayAdjustments getDisplayAdjustments(int displayId) {
+ // pass
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int getUserId() {
+ return 0; // not used
+ }
+
+ @Override
+ public File[] getExternalFilesDirs(String type) {
+ // pass
+ return new File[0];
+ }
+
+ @Override
+ public File[] getObbDirs() {
+ // pass
+ return new File[0];
+ }
+
+ @Override
+ public File[] getExternalCacheDirs() {
+ // pass
+ return new File[0];
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
new file mode 100644
index 0000000..3cf5ed5
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -0,0 +1,230 @@
+/*
+ * 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.android;
+
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.InputBindResult;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.text.style.SuggestionSpan;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.List;
+
+/**
+ * Basic implementation of IInputMethodManager that does nothing.
+ *
+ */
+public class BridgeIInputMethodManager implements IInputMethodManager {
+
+ @Override
+ public void addClient(IInputMethodClient arg0, IInputContext arg1, int arg2, int arg3)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void finishInput(IInputMethodClient arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public InputMethodSubtype getCurrentInputMethodSubtype() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodList() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String arg0,
+ boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public List<InputMethodInfo> getInputMethodList() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public InputMethodSubtype getLastInputMethodSubtype() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public List getShortcutInputMethodsAndSubtypes() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void hideMySoftInput(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean hideSoftInput(IInputMethodClient arg0, int arg1, ResultReceiver arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean notifySuggestionPicked(SuggestionSpan arg0, String arg1, int arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void registerSuggestionSpansForNotification(SuggestionSpan[] arg0)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void removeClient(IInputMethodClient arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAdditionalInputMethodSubtypes(String arg0, InputMethodSubtype[] arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean setCurrentInputMethodSubtype(InputMethodSubtype arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void setImeWindowStatus(IBinder arg0, int arg1, int arg2) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setInputMethod(IBinder arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setInputMethodAndSubtype(IBinder arg0, String arg1, InputMethodSubtype arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean setInputMethodEnabled(String arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void showInputMethodAndSubtypeEnablerFromClient(IInputMethodClient arg0, String arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void showInputMethodPickerFromClient(IInputMethodClient arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void showMySoftInput(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean showSoftInput(IInputMethodClient arg0, int arg1, ResultReceiver arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public InputBindResult startInput(IInputMethodClient client, IInputContext inputContext,
+ EditorInfo attribute, int controlFlags) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean switchToLastInputMethod(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean switchToNextInputMethod(IBinder arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean shouldOfferSwitchingToNextInputMethod(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void updateStatusIcon(IBinder arg0, String arg1, int arg2) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
+ int controlFlags, int softInputMode, int windowFlags, EditorInfo attribute,
+ IInputContext inputContext) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
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..f5912e7
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java
@@ -0,0 +1,164 @@
+/*
+ * 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;
+ }
+
+ @Override
+ 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.
+
+ @Override
+ public int getAttributeCount() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getAttributeName(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getAttributeValue(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPositionDescription() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeNameResource(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeListValue(String namespace, String attribute,
+ String[] options, int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(String namespace, String attribute,
+ boolean defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeResourceValue(String namespace, String attribute,
+ int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeIntValue(String namespace, String attribute,
+ int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(String namespace, String attribute,
+ int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public float getAttributeFloatValue(String namespace, String attribute,
+ float defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeListValue(int index,
+ String[] options, int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeResourceValue(int index, int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeIntValue(int index, int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public float getAttributeFloatValue(int index, float defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getIdAttribute() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getClassAttribute() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getIdAttributeResourceValue(int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getStyleAttribute() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
new file mode 100644
index 0000000..6fd5acc
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2012 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.os.IBinder;
+import android.os.IPowerManager;
+import android.os.RemoteException;
+import android.os.WorkSource;
+
+/**
+ * Fake implementation of IPowerManager.
+ *
+ */
+public class BridgePowerManager implements IPowerManager {
+
+ @Override
+ public boolean isScreenOn() throws RemoteException {
+ return true;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // pass for now.
+ return null;
+ }
+
+ @Override
+ public void acquireWakeLock(IBinder arg0, int arg1, String arg2, String arg2_5, WorkSource arg3)
+ throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void crash(String arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void goToSleep(long arg0, int arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void nap(long arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void reboot(boolean confirm, String reason, boolean wait) {
+ // pass for now.
+ }
+
+ @Override
+ public void shutdown(boolean confirm, boolean wait) {
+ // pass for now.
+ }
+
+ @Override
+ public void releaseWakeLock(IBinder arg0, int arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setAttentionLight(boolean arg0, int arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setTemporaryScreenBrightnessSettingOverride(int arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setMaximumScreenOffTimeoutFromDeviceAdmin(int arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setStayOnSetting(int arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void updateWakeLockWorkSource(IBinder arg0, WorkSource arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public boolean isWakeLockLevelSupported(int level) throws RemoteException {
+ // pass for now.
+ return true;
+ }
+
+ @Override
+ public void userActivity(long time, int event, int flags) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void wakeUp(long time) throws RemoteException {
+ // pass for now.
+ }
+}
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..df576d2
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -0,0 +1,107 @@
+/*
+ * 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;
+
+/**
+ * Implementation of {@link IWindow} to pass to the AttachInfo.
+ */
+public final class BridgeWindow implements IWindow {
+
+ @Override
+ public void dispatchAppVisibility(boolean arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchGetNewSurface() throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2)
+ throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void resized(Rect arg1, Rect arg1p5, Rect arg2, Rect arg3,
+ boolean arg4, Configuration arg5) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void moved(int arg0, int arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchScreenState(boolean on) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
+ boolean sync) {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchWallpaperCommand(String action, int x, int y,
+ int z, Bundle extras, boolean sync) {
+ // pass for now.
+ }
+
+ @Override
+ public void closeSystemDialogs(String reason) {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchDragEvent(DragEvent event) {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchSystemUiVisibilityChanged(int seq, int globalUi,
+ int localValue, int localChanges) {
+ // pass for now.
+ }
+
+ @Override
+ public void doneAnimating() {
+ }
+
+ @Override
+ 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..09e6878
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -0,0 +1,209 @@
+/*
+ * 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.IWindowId;
+import android.view.IWindowSession;
+import android.view.InputChannel;
+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 {
+
+ @Override
+ public int add(IWindow arg0, int seq, LayoutParams arg1, int arg2, Rect arg3,
+ InputChannel outInputchannel)
+ throws RemoteException {
+ // pass for now.
+ return 0;
+ }
+
+ @Override
+ public int addToDisplay(IWindow arg0, int seq, LayoutParams arg1, int arg2, int displayId,
+ Rect arg3, InputChannel outInputchannel)
+ throws RemoteException {
+ // pass for now.
+ return 0;
+ }
+
+ @Override
+ public int addWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2,
+ Rect arg3)
+ throws RemoteException {
+ // pass for now.
+ return 0;
+ }
+
+ @Override
+ public int addToDisplayWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2,
+ int displayId, Rect arg3)
+ throws RemoteException {
+ // pass for now.
+ return 0;
+ }
+
+ @Override
+ public void finishDrawing(IWindow arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public boolean getInTouchMode() throws RemoteException {
+ // pass for now.
+ return false;
+ }
+
+ @Override
+ public boolean performHapticFeedback(IWindow window, int effectId, boolean always) {
+ // pass for now.
+ return false;
+ }
+ @Override
+ public int relayout(IWindow arg0, int seq, LayoutParams arg1, int arg2, int arg3, int arg4,
+ int arg4_5, Rect arg5Z, Rect arg5, Rect arg6, Rect arg7, Configuration arg7b,
+ Surface arg8) throws RemoteException {
+ // pass for now.
+ return 0;
+ }
+
+ @Override
+ public void performDeferredDestroy(IWindow window) {
+ // pass for now.
+ }
+
+ @Override
+ public boolean outOfMemory(IWindow window) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
+ // pass for now.
+ }
+
+ @Override
+ public void remove(IWindow arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setInTouchMode(boolean arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setInsets(IWindow window, int touchable, Rect contentInsets,
+ Rect visibleInsets, Region touchableRegion) {
+ // pass for now.
+ }
+
+ @Override
+ public IBinder prepareDrag(IWindow window, int flags,
+ int thumbnailWidth, int thumbnailHeight, Surface outSurface)
+ throws RemoteException {
+ // pass for now
+ return null;
+ }
+
+ @Override
+ public boolean performDrag(IWindow window, IBinder dragToken,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data)
+ throws RemoteException {
+ // pass for now
+ return false;
+ }
+
+ @Override
+ public void reportDropResult(IWindow window, boolean consumed) throws RemoteException {
+ // pass for now
+ }
+
+ @Override
+ public void dragRecipientEntered(IWindow window) throws RemoteException {
+ // pass for now
+ }
+
+ @Override
+ public void dragRecipientExited(IWindow window) throws RemoteException {
+ // pass for now
+ }
+
+ @Override
+ public void setWallpaperPosition(IBinder window, float x, float y,
+ float xStep, float yStep) {
+ // pass for now.
+ }
+
+ @Override
+ public void wallpaperOffsetsComplete(IBinder window) {
+ // pass for now.
+ }
+
+ @Override
+ public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+ int z, Bundle extras, boolean sync) {
+ // pass for now.
+ return null;
+ }
+
+ @Override
+ public void wallpaperCommandComplete(IBinder window, Bundle result) {
+ // pass for now.
+ }
+
+ @Override
+ public void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
+ float dsdx, float dtdx, float dsdy, float dtdy) {
+ // pass for now.
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // pass for now.
+ return null;
+ }
+
+ @Override
+ public void onRectangleOnScreenRequested(IBinder window, Rect rectangle, boolean immediate) {
+ // pass for now.
+ }
+
+ @Override
+ public IWindowId getWindowId(IBinder window) throws RemoteException {
+ // pass for now.
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
new file mode 100644
index 0000000..ac8712e
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
@@ -0,0 +1,494 @@
+/*
+ * 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.android;
+
+
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.BridgeXmlPullAttributes;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * {@link BridgeXmlBlockParser} reimplements most of android.xml.XmlBlock.Parser.
+ * It delegates to both an instance of {@link XmlPullParser} and an instance of
+ * XmlPullAttributes (for the {@link AttributeSet} part).
+ */
+public class BridgeXmlBlockParser implements XmlResourceParser {
+
+ private final XmlPullParser mParser;
+ private final BridgeXmlPullAttributes mAttrib;
+ private final BridgeContext mContext;
+ private final boolean mPlatformFile;
+
+ private boolean mStarted = false;
+ private int mEventType = START_DOCUMENT;
+
+ private boolean mPopped = true; // default to true in case it's not pushed.
+
+ /**
+ * Builds a {@link BridgeXmlBlockParser}.
+ * @param parser The XmlPullParser to get the content from.
+ * @param context the Context.
+ * @param platformFile Indicates whether the the file is a platform file or not.
+ */
+ public BridgeXmlBlockParser(XmlPullParser parser, BridgeContext context, boolean platformFile) {
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("CRTE " + parser.toString());
+ }
+
+ mParser = parser;
+ mContext = context;
+ mPlatformFile = platformFile;
+ mAttrib = new BridgeXmlPullAttributes(parser, context, mPlatformFile);
+
+ if (mContext != null) {
+ mContext.pushParser(this);
+ mPopped = false;
+ }
+ }
+
+ public XmlPullParser getParser() {
+ return mParser;
+ }
+
+ public boolean isPlatformFile() {
+ return mPlatformFile;
+ }
+
+ 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
+
+ @Override
+ public void setFeature(String name, boolean state)
+ throws XmlPullParserException {
+ if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
+ return;
+ }
+ if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
+ return;
+ }
+ throw new XmlPullParserException("Unsupported feature: " + name);
+ }
+
+ @Override
+ public boolean getFeature(String name) {
+ if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
+ return true;
+ }
+ if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setProperty(String name, Object value) throws XmlPullParserException {
+ throw new XmlPullParserException("setProperty() not supported");
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ return null;
+ }
+
+ @Override
+ public void setInput(Reader in) throws XmlPullParserException {
+ mParser.setInput(in);
+ }
+
+ @Override
+ public void setInput(InputStream inputStream, String inputEncoding)
+ throws XmlPullParserException {
+ mParser.setInput(inputStream, inputEncoding);
+ }
+
+ @Override
+ public void defineEntityReplacementText(String entityName,
+ String replacementText) throws XmlPullParserException {
+ throw new XmlPullParserException(
+ "defineEntityReplacementText() not supported");
+ }
+
+ @Override
+ public String getNamespacePrefix(int pos) throws XmlPullParserException {
+ throw new XmlPullParserException("getNamespacePrefix() not supported");
+ }
+
+ @Override
+ public String getInputEncoding() {
+ return null;
+ }
+
+ @Override
+ public String getNamespace(String prefix) {
+ throw new RuntimeException("getNamespace() not supported");
+ }
+
+ @Override
+ public int getNamespaceCount(int depth) throws XmlPullParserException {
+ throw new XmlPullParserException("getNamespaceCount() not supported");
+ }
+
+ @Override
+ public String getPositionDescription() {
+ return "Binary XML file line #" + getLineNumber();
+ }
+
+ @Override
+ public String getNamespaceUri(int pos) throws XmlPullParserException {
+ throw new XmlPullParserException("getNamespaceUri() not supported");
+ }
+
+ @Override
+ public int getColumnNumber() {
+ return -1;
+ }
+
+ @Override
+ public int getDepth() {
+ return mParser.getDepth();
+ }
+
+ @Override
+ public String getText() {
+ return mParser.getText();
+ }
+
+ @Override
+ public int getLineNumber() {
+ return mParser.getLineNumber();
+ }
+
+ @Override
+ public int getEventType() {
+ return mEventType;
+ }
+
+ @Override
+ public boolean isWhitespace() throws XmlPullParserException {
+ // Original comment: whitespace was stripped by aapt.
+ return mParser.isWhitespace();
+ }
+
+ @Override
+ public String getPrefix() {
+ throw new RuntimeException("getPrefix not supported");
+ }
+
+ @Override
+ public char[] getTextCharacters(int[] holderForStartAndLength) {
+ String txt = getText();
+ char[] chars = null;
+ if (txt != null) {
+ holderForStartAndLength[0] = 0;
+ holderForStartAndLength[1] = txt.length();
+ chars = new char[txt.length()];
+ txt.getChars(0, txt.length(), chars, 0);
+ }
+ return chars;
+ }
+
+ @Override
+ public String getNamespace() {
+ return mParser.getNamespace();
+ }
+
+ @Override
+ public String getName() {
+ return mParser.getName();
+ }
+
+ @Override
+ public String getAttributeNamespace(int index) {
+ return mParser.getAttributeNamespace(index);
+ }
+
+ @Override
+ public String getAttributeName(int index) {
+ return mParser.getAttributeName(index);
+ }
+
+ @Override
+ public String getAttributePrefix(int index) {
+ throw new RuntimeException("getAttributePrefix not supported");
+ }
+
+ @Override
+ public boolean isEmptyElementTag() {
+ // XXX Need to detect this.
+ return false;
+ }
+
+ @Override
+ public int getAttributeCount() {
+ return mParser.getAttributeCount();
+ }
+
+ @Override
+ public String getAttributeValue(int index) {
+ return mParser.getAttributeValue(index);
+ }
+
+ @Override
+ public String getAttributeType(int index) {
+ return "CDATA";
+ }
+
+ @Override
+ public boolean isAttributeDefault(int index) {
+ return false;
+ }
+
+ @Override
+ public int nextToken() throws XmlPullParserException, IOException {
+ return next();
+ }
+
+ @Override
+ public String getAttributeValue(String namespace, String name) {
+ return mParser.getAttributeValue(namespace, name);
+ }
+
+ @Override
+ public int next() throws XmlPullParserException, IOException {
+ if (!mStarted) {
+ mStarted = true;
+
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("STRT " + mParser.toString());
+ }
+
+ return START_DOCUMENT;
+ }
+
+ int ev = mParser.next();
+
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("NEXT " + mParser.toString() + " " +
+ eventTypeToString(mEventType) + " -> " + eventTypeToString(ev));
+ }
+
+ if (ev == END_TAG && mParser.getDepth() == 1) {
+ // done with parser remove it from the context stack.
+ ensurePopped();
+
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("");
+ }
+ }
+
+ mEventType = ev;
+ return ev;
+ }
+
+ public static String eventTypeToString(int eventType) {
+ switch (eventType) {
+ case START_DOCUMENT:
+ return "START_DOC";
+ case END_DOCUMENT:
+ return "END_DOC";
+ case START_TAG:
+ return "START_TAG";
+ case END_TAG:
+ return "END_TAG";
+ case TEXT:
+ return "TEXT";
+ case CDSECT:
+ return "CDSECT";
+ case ENTITY_REF:
+ return "ENTITY_REF";
+ case IGNORABLE_WHITESPACE:
+ return "IGNORABLE_WHITESPACE";
+ case PROCESSING_INSTRUCTION:
+ return "PROCESSING_INSTRUCTION";
+ case COMMENT:
+ return "COMMENT";
+ case DOCDECL:
+ return "DOCDECL";
+ }
+
+ return "????";
+ }
+
+ @Override
+ public void require(int type, String namespace, String name)
+ throws XmlPullParserException {
+ if (type != getEventType()
+ || (namespace != null && !namespace.equals(getNamespace()))
+ || (name != null && !name.equals(getName())))
+ throw new XmlPullParserException("expected " + TYPES[type]
+ + getPositionDescription());
+ }
+
+ @Override
+ public String nextText() throws XmlPullParserException, IOException {
+ if (getEventType() != START_TAG) {
+ throw new XmlPullParserException(getPositionDescription()
+ + ": parser must be on START_TAG to read next text", this,
+ null);
+ }
+ int eventType = next();
+ if (eventType == TEXT) {
+ String result = getText();
+ eventType = next();
+ if (eventType != END_TAG) {
+ throw new XmlPullParserException(
+ getPositionDescription()
+ + ": event TEXT it must be immediately followed by END_TAG",
+ this, null);
+ }
+ return result;
+ } else if (eventType == END_TAG) {
+ return "";
+ } else {
+ throw new XmlPullParserException(getPositionDescription()
+ + ": parser must be on START_TAG or TEXT to read text",
+ this, null);
+ }
+ }
+
+ @Override
+ public int nextTag() throws XmlPullParserException, IOException {
+ int eventType = next();
+ if (eventType == TEXT && isWhitespace()) { // skip whitespace
+ eventType = next();
+ }
+ if (eventType != START_TAG && eventType != END_TAG) {
+ throw new XmlPullParserException(getPositionDescription()
+ + ": expected start or end tag", this, null);
+ }
+ return eventType;
+ }
+
+ // AttributeSet implementation
+
+
+ @Override
+ public void close() {
+ // pass
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+ return mAttrib.getAttributeBooleanValue(index, defaultValue);
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(String namespace, String attribute,
+ boolean defaultValue) {
+ return mAttrib.getAttributeBooleanValue(namespace, attribute, defaultValue);
+ }
+
+ @Override
+ public float getAttributeFloatValue(int index, float defaultValue) {
+ return mAttrib.getAttributeFloatValue(index, defaultValue);
+ }
+
+ @Override
+ public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) {
+ return mAttrib.getAttributeFloatValue(namespace, attribute, defaultValue);
+ }
+
+ @Override
+ public int getAttributeIntValue(int index, int defaultValue) {
+ return mAttrib.getAttributeIntValue(index, defaultValue);
+ }
+
+ @Override
+ public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
+ return mAttrib.getAttributeIntValue(namespace, attribute, defaultValue);
+ }
+
+ @Override
+ public int getAttributeListValue(int index, String[] options, int defaultValue) {
+ return mAttrib.getAttributeListValue(index, options, defaultValue);
+ }
+
+ @Override
+ public int getAttributeListValue(String namespace, String attribute,
+ String[] options, int defaultValue) {
+ return mAttrib.getAttributeListValue(namespace, attribute, options, defaultValue);
+ }
+
+ @Override
+ public int getAttributeNameResource(int index) {
+ return mAttrib.getAttributeNameResource(index);
+ }
+
+ @Override
+ public int getAttributeResourceValue(int index, int defaultValue) {
+ return mAttrib.getAttributeResourceValue(index, defaultValue);
+ }
+
+ @Override
+ public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
+ return mAttrib.getAttributeResourceValue(namespace, attribute, defaultValue);
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+ return mAttrib.getAttributeUnsignedIntValue(index, defaultValue);
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) {
+ return mAttrib.getAttributeUnsignedIntValue(namespace, attribute, defaultValue);
+ }
+
+ @Override
+ public String getClassAttribute() {
+ return mAttrib.getClassAttribute();
+ }
+
+ @Override
+ public String getIdAttribute() {
+ return mAttrib.getIdAttribute();
+ }
+
+ @Override
+ public int getIdAttributeResourceValue(int defaultValue) {
+ return mAttrib.getIdAttributeResourceValue(defaultValue);
+ }
+
+ @Override
+ public int getStyleAttribute() {
+ return mAttrib.getStyleAttribute();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
new file mode 100644
index 0000000..9a633bf
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 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.view;
+
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.View;
+import android.view.WindowManager;
+
+public class WindowManagerImpl implements WindowManager {
+
+ private final DisplayMetrics mMetrics;
+ private final Display mDisplay;
+
+ public WindowManagerImpl(DisplayMetrics metrics) {
+ mMetrics = metrics;
+
+ DisplayInfo info = new DisplayInfo();
+ info.logicalHeight = mMetrics.heightPixels;
+ info.logicalWidth = mMetrics.widthPixels;
+ mDisplay = new Display(null, Display.DEFAULT_DISPLAY, info, null);
+ }
+
+ @Override
+ public Display getDefaultDisplay() {
+ return mDisplay;
+ }
+
+
+ @Override
+ public void addView(View arg0, android.view.ViewGroup.LayoutParams arg1) {
+ // pass
+ }
+
+ @Override
+ public void removeView(View arg0) {
+ // pass
+ }
+
+ @Override
+ public void updateViewLayout(View arg0, android.view.ViewGroup.LayoutParams arg1) {
+ // pass
+ }
+
+
+ @Override
+ public void removeViewImmediate(View arg0) {
+ // pass
+ }
+}
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..ea9d8d9
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -0,0 +1,307 @@
+/*
+ * 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.ParserFactory;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.resources.Density;
+import com.android.resources.ResourceType;
+
+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, int orientation, String layoutPath,
+ String name) throws XmlPullParserException {
+ super(context);
+ setOrientation(orientation);
+ if (orientation == LinearLayout.HORIZONTAL) {
+ setGravity(Gravity.CENTER_VERTICAL);
+ } else {
+ setGravity(Gravity.CENTER_HORIZONTAL);
+ }
+
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ XmlPullParser parser = ParserFactory.create(getClass().getResourceAsStream(layoutPath),
+ name);
+
+ 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.setImageDrawable(drawable);
+ }
+ }
+ }
+ }
+
+ protected void loadIcon(int index, String iconReference) {
+ ResourceValue value = getResourceValue(iconReference);
+ if (value != null) {
+ loadIcon(index, value);
+ }
+ }
+
+ protected void loadIconById(int id, String iconReference) {
+ ResourceValue value = getResourceValue(iconReference);
+ if (value != null) {
+ loadIconById(id, 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;
+
+ return loadIcon(imageView, value);
+ }
+
+ return null;
+ }
+
+ private Drawable loadIconById(int id, ResourceValue value) {
+ View child = findViewById(id);
+ if (child instanceof ImageView) {
+ ImageView imageView = (ImageView) child;
+
+ return loadIcon(imageView, value);
+ }
+
+ return null;
+ }
+
+
+ private Drawable loadIcon(ImageView imageView, ResourceValue value) {
+ Drawable drawable = ResourceHelper.getDrawable(value, (BridgeContext) mContext);
+ if (drawable != null) {
+ imageView.setImageDrawable(drawable);
+ }
+
+ return drawable;
+ }
+
+ protected TextView setText(int index, String stringReference) {
+ View child = getChildAt(index);
+ if (child instanceof TextView) {
+ TextView textView = (TextView) child;
+ setText(textView, stringReference);
+ return textView;
+ }
+
+ return null;
+ }
+
+ protected TextView setTextById(int id, String stringReference) {
+ View child = findViewById(id);
+ if (child instanceof TextView) {
+ TextView textView = (TextView) child;
+ setText(textView, stringReference);
+ return textView;
+ }
+
+ return null;
+ }
+
+ private void setText(TextView textView, String stringReference) {
+ ResourceValue value = getResourceValue(stringReference);
+ if (value != null) {
+ textView.setText(value.getValue());
+ } else {
+ textView.setText(stringReference);
+ }
+ }
+
+ protected void setStyle(String themeEntryName) {
+
+ BridgeContext bridgeContext = (BridgeContext) mContext;
+ RenderResources res = bridgeContext.getRenderResources();
+
+ ResourceValue value = res.findItemInTheme(themeEntryName, true /*isFrameworkAttr*/);
+ value = res.resolveResValue(value);
+
+ if (value instanceof StyleResourceValue == false) {
+ return;
+ }
+
+ StyleResourceValue style = (StyleResourceValue) value;
+
+ // get the background
+ ResourceValue backgroundValue = res.findItemInStyle(style, "background",
+ true /*isFrameworkAttr*/);
+ backgroundValue = res.resolveResValue(backgroundValue);
+ if (backgroundValue != null) {
+ Drawable d = ResourceHelper.getDrawable(backgroundValue, bridgeContext);
+ if (d != null) {
+ setBackground(d);
+ }
+ }
+
+ TextView textView = getStyleableTextView();
+ if (textView != null) {
+ // get the text style
+ ResourceValue textStyleValue = res.findItemInStyle(style, "titleTextStyle",
+ true /*isFrameworkAttr*/);
+ textStyleValue = res.resolveResValue(textStyleValue);
+ if (textStyleValue instanceof StyleResourceValue) {
+ StyleResourceValue textStyle = (StyleResourceValue) textStyleValue;
+
+ ResourceValue textSize = res.findItemInStyle(textStyle, "textSize",
+ true /*isFrameworkAttr*/);
+ textSize = res.resolveResValue(textSize);
+
+ if (textSize != null) {
+ TypedValue out = new TypedValue();
+ if (ResourceHelper.parseFloatAttribute("textSize", textSize.getValue(), out,
+ true /*requireUnit*/)) {
+ textView.setTextSize(
+ out.getDimension(bridgeContext.getResources().getDisplayMetrics()));
+ }
+ }
+
+
+ ResourceValue textColor = res.findItemInStyle(textStyle, "textColor",
+ true /*isFrameworkAttr*/);
+ 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..226649d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.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.layoutlib.bridge.bars;
+
+import com.android.resources.Density;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+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, LinearLayout.HORIZONTAL, "/bars/action_bar.xml", "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.
+ loadIconById(android.R.id.home, 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/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
new file mode 100644
index 0000000..cc90d6b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.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 org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class NavigationBar extends CustomBar {
+
+ public NavigationBar(Context context, Density density, int orientation) throws XmlPullParserException {
+ super(context, density, orientation, "/bars/navigation_bar.xml", "navigation_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.
+ // 0 is a spacer.
+ int back = 1;
+ int recent = 3;
+ if (orientation == LinearLayout.VERTICAL) {
+ back = 3;
+ recent = 1;
+ }
+
+ loadIcon(back, "ic_sysbar_back.png", density);
+ loadIcon(2, "ic_sysbar_home.png", density);
+ loadIcon(recent, "ic_sysbar_recent.png", density);
+ }
+
+ @Override
+ protected TextView getStyleableTextView() {
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
new file mode 100644
index 0000000..5c08412
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
@@ -0,0 +1,55 @@
+/*
+ * 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.LinearLayout;
+import android.widget.TextView;
+
+public class StatusBar extends CustomBar {
+
+ public StatusBar(Context context, Density density) throws XmlPullParserException {
+ super(context, density, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml");
+
+ // FIXME: use FILL_H?
+ setGravity(Gravity.START | Gravity.TOP | 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/TitleBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java
new file mode 100644
index 0000000..c27859f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.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.LinearLayout;
+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, LinearLayout.HORIZONTAL, "/bars/title_bar.xml", "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/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/impl/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
new file mode 100644
index 0000000..081ce67
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
@@ -0,0 +1,378 @@
+/*
+ * 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.impl;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import android.graphics.Typeface;
+
+import java.awt.Font;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Provides {@link Font} object to the layout lib.
+ * <p/>
+ * The fonts are loaded from the SDK directory. Family/style mapping is done by parsing the
+ * fonts.xml file located alongside the ttf files.
+ */
+public final class FontLoader {
+ private static final String FONTS_SYSTEM = "system_fonts.xml";
+ private static final String FONTS_VENDOR = "vendor_fonts.xml";
+ private static final String FONTS_FALLBACK = "fallback_fonts.xml";
+
+ private static final String NODE_FAMILYSET = "familyset";
+ private static final String NODE_FAMILY = "family";
+ private static final String NODE_NAME = "name";
+ private static final String NODE_FILE = "file";
+
+ private static final String FONT_SUFFIX_NONE = ".ttf";
+ private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf";
+ private static final String FONT_SUFFIX_BOLD = "-Bold.ttf";
+ private static final String FONT_SUFFIX_ITALIC = "-Italic.ttf";
+ private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf";
+
+ // This must match the values of Typeface styles so that we can use them for indices in this
+ // array.
+ private static final int[] AWT_STYLES = new int[] {
+ Font.PLAIN,
+ Font.BOLD,
+ Font.ITALIC,
+ Font.BOLD | Font.ITALIC
+ };
+ private static int[] DERIVE_BOLD_ITALIC = new int[] {
+ Typeface.ITALIC, Typeface.BOLD, Typeface.NORMAL
+ };
+ private static int[] DERIVE_ITALIC = new int[] { Typeface.NORMAL };
+ private static int[] DERIVE_BOLD = new int[] { Typeface.NORMAL };
+
+ private static final List<FontInfo> mMainFonts = new ArrayList<FontInfo>();
+ private static final List<FontInfo> mFallbackFonts = new ArrayList<FontInfo>();
+
+ private final String mOsFontsLocation;
+
+ public static FontLoader create(String fontOsLocation) {
+ try {
+ SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+ parserFactory.setNamespaceAware(true);
+
+ // parse the system fonts
+ FontHandler handler = parseFontFile(parserFactory, fontOsLocation, FONTS_SYSTEM);
+ List<FontInfo> systemFonts = handler.getFontList();
+
+
+ // parse the fallback fonts
+ handler = parseFontFile(parserFactory, fontOsLocation, FONTS_FALLBACK);
+ List<FontInfo> fallbackFonts = handler.getFontList();
+
+ return new FontLoader(fontOsLocation, systemFonts, fallbackFonts);
+ } catch (ParserConfigurationException e) {
+ // return null below
+ } catch (SAXException e) {
+ // return null below
+ } catch (FileNotFoundException e) {
+ // return null below
+ } catch (IOException e) {
+ // return null below
+ }
+
+ return null;
+ }
+
+ private static FontHandler parseFontFile(SAXParserFactory parserFactory,
+ String fontOsLocation, String fontFileName)
+ throws ParserConfigurationException, SAXException, IOException, FileNotFoundException {
+
+ SAXParser parser = parserFactory.newSAXParser();
+ File f = new File(fontOsLocation, fontFileName);
+
+ FontHandler definitionParser = new FontHandler(
+ fontOsLocation + File.separator);
+ parser.parse(new FileInputStream(f), definitionParser);
+ return definitionParser;
+ }
+
+ private FontLoader(String fontOsLocation,
+ List<FontInfo> fontList, List<FontInfo> fallBackList) {
+ mOsFontsLocation = fontOsLocation;
+ mMainFonts.addAll(fontList);
+ mFallbackFonts.addAll(fallBackList);
+ }
+
+
+ public String getOsFontsLocation() {
+ return mOsFontsLocation;
+ }
+
+ /**
+ * 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 List<Font> getFont(String family, int style) {
+ List<Font> result = new ArrayList<Font>();
+
+ if (family == null) {
+ return result;
+ }
+
+
+ // get the font objects from the main list based on family.
+ for (FontInfo info : mMainFonts) {
+ if (info.families.contains(family)) {
+ result.add(info.font[style]);
+ break;
+ }
+ }
+
+ // add all the fallback fonts for the given style
+ for (FontInfo info : mFallbackFonts) {
+ result.add(info.font[style]);
+ }
+
+ return result;
+ }
+
+
+ public synchronized List<Font> getFallbackFonts(int style) {
+ List<Font> result = new ArrayList<Font>();
+ // add all the fallback fonts
+ for (FontInfo info : mFallbackFonts) {
+ result.add(info.font[style]);
+ }
+ return result;
+ }
+
+
+ private final static class FontInfo {
+ final Font[] font = new Font[4]; // Matches the 4 type-face styles.
+ final Set<String> families;
+
+ FontInfo() {
+ families = new HashSet<String>();
+ }
+ }
+
+ private final static class FontHandler extends DefaultHandler {
+ private final String mOsFontsLocation;
+
+ private FontInfo mFontInfo = null;
+ private final StringBuilder mBuilder = new StringBuilder();
+ private List<FontInfo> mFontList = new ArrayList<FontInfo>();
+
+ private FontHandler(String osFontsLocation) {
+ super();
+ mOsFontsLocation = osFontsLocation;
+ }
+
+ public List<FontInfo> getFontList() {
+ return mFontList;
+ }
+
+ /* (non-Javadoc)
+ * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ @Override
+ public void startElement(String uri, String localName, String name, Attributes attributes)
+ throws SAXException {
+ if (NODE_FAMILYSET.equals(localName)) {
+ mFontList = new ArrayList<FontInfo>();
+ } else if (NODE_FAMILY.equals(localName)) {
+ if (mFontList != null) {
+ mFontInfo = new FontInfo();
+ }
+ }
+
+ mBuilder.setLength(0);
+
+ super.startElement(uri, localName, name, attributes);
+ }
+
+ /* (non-Javadoc)
+ * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
+ */
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ mBuilder.append(ch, start, length);
+ }
+
+ /* (non-Javadoc)
+ * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
+ */
+ @Override
+ public void endElement(String uri, String localName, String name) throws SAXException {
+ if (NODE_FAMILY.equals(localName)) {
+ if (mFontInfo != null) {
+ // if has a normal font file, add to the list
+ if (mFontInfo.font[Typeface.NORMAL] != null) {
+ mFontList.add(mFontInfo);
+
+ // create missing font styles, order is important.
+ if (mFontInfo.font[Typeface.BOLD_ITALIC] == null) {
+ computeDerivedFont(Typeface.BOLD_ITALIC, DERIVE_BOLD_ITALIC);
+ }
+ if (mFontInfo.font[Typeface.ITALIC] == null) {
+ computeDerivedFont(Typeface.ITALIC, DERIVE_ITALIC);
+ }
+ if (mFontInfo.font[Typeface.BOLD] == null) {
+ computeDerivedFont(Typeface.BOLD, DERIVE_BOLD);
+ }
+ }
+
+ mFontInfo = null;
+ }
+ } else if (NODE_NAME.equals(localName)) {
+ // handle a new name for an existing Font Info
+ if (mFontInfo != null) {
+ String family = trimXmlWhitespaces(mBuilder.toString());
+ mFontInfo.families.add(family);
+ }
+ } else if (NODE_FILE.equals(localName)) {
+ // handle a new file for an existing Font Info
+ if (mFontInfo != null) {
+ String fileName = trimXmlWhitespaces(mBuilder.toString());
+ Font font = getFont(fileName);
+ if (font != null) {
+ if (fileName.endsWith(FONT_SUFFIX_REGULAR)) {
+ mFontInfo.font[Typeface.NORMAL] = font;
+ } else if (fileName.endsWith(FONT_SUFFIX_BOLD)) {
+ mFontInfo.font[Typeface.BOLD] = font;
+ } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) {
+ mFontInfo.font[Typeface.ITALIC] = font;
+ } else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) {
+ mFontInfo.font[Typeface.BOLD_ITALIC] = font;
+ } else if (fileName.endsWith(FONT_SUFFIX_NONE)) {
+ mFontInfo.font[Typeface.NORMAL] = font;
+ }
+ }
+ }
+ }
+ }
+
+ private Font getFont(String fileName) {
+ try {
+ File file = new File(mOsFontsLocation, fileName);
+ if (file.exists()) {
+ return Font.createFont(Font.TRUETYPE_FONT, file);
+ }
+ } catch (Exception e) {
+
+ }
+
+ return null;
+ }
+
+ private void computeDerivedFont( int toCompute, int[] basedOnList) {
+ for (int basedOn : basedOnList) {
+ if (mFontInfo.font[basedOn] != null) {
+ mFontInfo.font[toCompute] =
+ mFontInfo.font[basedOn].deriveFont(AWT_STYLES[toCompute]);
+ return;
+ }
+ }
+
+ // we really shouldn't stop there. This means we don't have a NORMAL font...
+ assert false;
+ }
+
+ private String trimXmlWhitespaces(String value) {
+ if (value == null) {
+ return null;
+ }
+
+ // look for carriage return and replace all whitespace around it by just 1 space.
+ int index;
+
+ while ((index = value.indexOf('\n')) != -1) {
+ // look for whitespace on each side
+ int left = index - 1;
+ while (left >= 0) {
+ if (Character.isWhitespace(value.charAt(left))) {
+ left--;
+ } else {
+ break;
+ }
+ }
+
+ int right = index + 1;
+ int count = value.length();
+ while (right < count) {
+ if (Character.isWhitespace(value.charAt(right))) {
+ right++;
+ } else {
+ break;
+ }
+ }
+
+ // remove all between left and right (non inclusive) and replace by a single space.
+ String leftString = null;
+ if (left >= 0) {
+ leftString = value.substring(0, left + 1);
+ }
+ String rightString = null;
+ if (right < count) {
+ rightString = value.substring(right);
+ }
+
+ if (leftString != null) {
+ value = leftString;
+ if (rightString != null) {
+ value += " " + rightString;
+ }
+ } else {
+ value = rightString != null ? rightString : "";
+ }
+ }
+
+ // now we un-escape the string
+ int length = value.length();
+ char[] buffer = value.toCharArray();
+
+ for (int i = 0 ; i < length ; i++) {
+ if (buffer[i] == '\\') {
+ if (buffer[i+1] == 'n') {
+ // replace the char with \n
+ buffer[i+1] = '\n';
+ }
+
+ // offset the rest of the buffer since we go from 2 to 1 char
+ System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
+ length--;
+ }
+ }
+
+ return new String(buffer, 0, length);
+ }
+
+ }
+}
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/ParserFactory.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
new file mode 100644
index 0000000..803849f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
@@ -0,0 +1,142 @@
+/*
+ * 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 org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A factory for {@link XmlPullParser}.
+ *
+ */
+public class ParserFactory {
+
+ private final static String ENCODING = "UTF-8"; //$NON-NLS-1$
+
+ public final static boolean LOG_PARSER = false;
+
+ public static XmlPullParser create(File f)
+ throws XmlPullParserException, FileNotFoundException {
+ InputStream stream = new FileInputStream(f);
+ return create(stream, f.getName(), f.length());
+ }
+
+ public static XmlPullParser create(InputStream stream, String name)
+ throws XmlPullParserException {
+ return create(stream, name, -1);
+ }
+
+ private static XmlPullParser create(InputStream stream, String name, long size)
+ throws XmlPullParserException {
+ KXmlParser parser = instantiateParser(name);
+
+ stream = readAndClose(stream, name, size);
+
+ parser.setInput(stream, ENCODING);
+ return parser;
+ }
+
+ private static KXmlParser instantiateParser(String name) throws XmlPullParserException {
+ KXmlParser parser;
+ if (name != null) {
+ parser = new CustomParser(name);
+ } else {
+ parser = new KXmlParser();
+ }
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ return parser;
+ }
+
+ private static InputStream readAndClose(InputStream stream, String name, long size)
+ throws XmlPullParserException {
+ // just a sanity check. It's doubtful we'll have such big files!
+ if (size > Integer.MAX_VALUE) {
+ throw new XmlPullParserException("File " + name + " is too big to be parsed");
+ }
+ int intSize = (int) size;
+
+ // create a buffered reader to facilitate reading.
+ BufferedInputStream bufferedStream = new BufferedInputStream(stream);
+ try {
+ int avail;
+ if (intSize != -1) {
+ avail = intSize;
+ } else {
+ // get the size to read.
+ avail = bufferedStream.available();
+ }
+
+ // create the initial buffer and read it.
+ byte[] buffer = new byte[avail];
+ int read = stream.read(buffer);
+
+ // this is the easy case.
+ if (read == intSize) {
+ return new ByteArrayInputStream(buffer);
+ }
+
+ // check if there is more to read (read() does not necessarily read all that
+ // available() returned!)
+ while ((avail = bufferedStream.available()) > 0) {
+ if (read + avail > buffer.length) {
+ // just allocate what is needed. We're mostly reading small files
+ // so it shouldn't be too problematic.
+ byte[] moreBuffer = new byte[read + avail];
+ System.arraycopy(buffer, 0, moreBuffer, 0, read);
+ buffer = moreBuffer;
+ }
+
+ read += stream.read(buffer, read, avail);
+ }
+
+ // return a new stream encapsulating this buffer.
+ return new ByteArrayInputStream(buffer);
+
+ } catch (IOException e) {
+ throw new XmlPullParserException("Failed to read " + name, null, e);
+ } finally {
+ try {
+ bufferedStream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ private static class CustomParser extends KXmlParser {
+ private final String mName;
+
+ CustomParser(String name) {
+ super();
+ mName = name;
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+ }
+}
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..7b70180
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java
@@ -0,0 +1,49 @@
+/*
+ * 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.AnimationThread;
+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..b909bec
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -0,0 +1,380 @@
+/*
+ * 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.HardwareConfig;
+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.RenderResources.FrameworkResourceIdProvider;
+import com.android.ide.common.rendering.api.Result;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.resources.Density;
+import com.android.resources.ResourceType;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenSize;
+
+import android.content.res.Configuration;
+import android.os.HandlerThread_Delegate;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.view.ViewConfiguration_Accessor;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodManager_Accessor;
+
+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;
+ }
+
+ HardwareConfig hardwareConfig = mParams.getHardwareConfig();
+
+ // setup the display Metrics.
+ DisplayMetrics metrics = new DisplayMetrics();
+ metrics.densityDpi = metrics.noncompatDensityDpi =
+ hardwareConfig.getDensity().getDpiValue();
+
+ metrics.density = metrics.noncompatDensity =
+ metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
+
+ metrics.scaledDensity = metrics.noncompatScaledDensity = metrics.density;
+
+ metrics.widthPixels = metrics.noncompatWidthPixels = hardwareConfig.getScreenWidth();
+ metrics.heightPixels = metrics.noncompatHeightPixels = hardwareConfig.getScreenHeight();
+ metrics.xdpi = metrics.noncompatXdpi = hardwareConfig.getXdpi();
+ metrics.ydpi = metrics.noncompatYdpi = hardwareConfig.getYdpi();
+
+ RenderResources resources = mParams.getResources();
+
+ // build the context
+ mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
+ mParams.getProjectCallback(), getConfiguration(), 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;
+
+ // create an InputMethodManager
+ InputMethodManager.getInstance();
+
+ 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();
+
+ // quit HandlerThread created during this session.
+ HandlerThread_Delegate.cleanUp(sCurrentContext);
+
+ // clear the stored ViewConfiguration since the map is per density and not per context.
+ ViewConfiguration_Accessor.clearConfigurations();
+
+ // remove the InputMethodManager
+ InputMethodManager_Accessor.resetInstance();
+
+ 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");
+ }
+ }
+
+ private Configuration getConfiguration() {
+ Configuration config = new Configuration();
+
+ HardwareConfig hardwareConfig = mParams.getHardwareConfig();
+
+ ScreenSize screenSize = hardwareConfig.getScreenSize();
+ if (screenSize != null) {
+ switch (screenSize) {
+ case SMALL:
+ config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_SMALL;
+ break;
+ case NORMAL:
+ config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_NORMAL;
+ break;
+ case LARGE:
+ config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_LARGE;
+ break;
+ case XLARGE:
+ config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ break;
+ }
+ }
+
+ Density density = hardwareConfig.getDensity();
+ if (density == null) {
+ density = Density.MEDIUM;
+ }
+
+ config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue();
+ config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue();
+ if (config.screenHeightDp < config.screenWidthDp) {
+ config.smallestScreenWidthDp = config.screenHeightDp;
+ } else {
+ config.smallestScreenWidthDp = config.screenWidthDp;
+ }
+ config.densityDpi = density.getDpiValue();
+
+ // never run in compat mode:
+ config.compatScreenWidthDp = config.screenWidthDp;
+ config.compatScreenHeightDp = config.screenHeightDp;
+
+ ScreenOrientation orientation = hardwareConfig.getOrientation();
+ if (orientation != null) {
+ switch (orientation) {
+ case PORTRAIT:
+ config.orientation = Configuration.ORIENTATION_PORTRAIT;
+ break;
+ case LANDSCAPE:
+ config.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ break;
+ case SQUARE:
+ config.orientation = Configuration.ORIENTATION_SQUARE;
+ break;
+ }
+ } else {
+ config.orientation = Configuration.ORIENTATION_UNDEFINED;
+ }
+
+ // TODO: fill in more config info.
+
+ return config;
+ }
+
+
+ // --- 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..b677131
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
@@ -0,0 +1,133 @@
+/*
+ * 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.HardwareConfig;
+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.resources.ResourceType;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap_Delegate;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.view.AttachInfo_Accessor;
+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();
+ HardwareConfig hardwareConfig = params.getHardwareConfig();
+ 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.setBackground(d);
+
+ // set the AttachInfo on the root view.
+ AttachInfo_Accessor.setAttachInfo(content);
+
+
+ // measure
+ int w = hardwareConfig.getScreenWidth();
+ int h = hardwareConfig.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
+ AttachInfo_Accessor.dispatchOnPreDraw(content);
+
+ // 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*/, hardwareConfig.getDensity());
+
+ // create a Canvas around the Android bitmap
+ Canvas canvas = new Canvas(bitmap);
+ canvas.setDensity(hardwareConfig.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..6011fdb
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -0,0 +1,1471 @@
+/*
+ * 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.AdapterBinding;
+import com.android.ide.common.rendering.api.HardwareConfig;
+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.ResourceReference;
+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.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.rendering.api.ViewInfo;
+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.BridgeLayoutParamsMapAttributes;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.bars.FakeActionBar;
+import com.android.layoutlib.bridge.bars.NavigationBar;
+import com.android.layoutlib.bridge.bars.StatusBar;
+import com.android.layoutlib.bridge.bars.TitleBar;
+import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
+import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
+import com.android.resources.ResourceType;
+import com.android.resources.ScreenOrientation;
+import com.android.util.Pair;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.animation.AnimationThread;
+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.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.AttachInfo_Accessor;
+import android.view.BridgeInflater;
+import android.view.IWindowManager;
+import android.view.IWindowManagerImpl;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.WindowManagerGlobal_Delegate;
+import android.widget.AbsListView;
+import android.widget.AbsSpinner;
+import android.widget.AdapterView;
+import android.widget.ExpandableListView;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.QuickContactBadge;
+import android.widget.TabHost;
+import android.widget.TabHost.TabSpec;
+import android.widget.TabWidget;
+
+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 mNavigationBarSize;
+ private int mNavigationBarOrientation = LinearLayout.HORIZONTAL;
+ 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);
+ findNavigationBar(resources, metrics);
+
+ // FIXME: find those out, and possibly add them to the render params
+ boolean hasNavigationBar = true;
+ IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
+ metrics, Surface.ROTATION_0,
+ hasNavigationBar);
+ WindowManagerGlobal_Delegate.setWindowManagerService(iwm);
+
+ // build the inflater and parser.
+ mInflater = new BridgeInflater(context, params.getProjectCallback());
+ context.setBridgeInflater(mInflater);
+
+ 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();
+ HardwareConfig hardwareConfig = params.getHardwareConfig();
+ 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 {
+ if (hasSoftwareButtons() && mNavigationBarOrientation == LinearLayout.VERTICAL) {
+ /*
+ * This is a special case where the navigation bar is on the right.
+ +-------------------------------------------------+---+
+ | Status bar (always) | |
+ +-------------------------------------------------+ |
+ | (Layout with background drawable) | |
+ | +---------------------------------------------+ | |
+ | | Title/Action bar (optional) | | |
+ | +---------------------------------------------+ | |
+ | | Content, vertical extending | | |
+ | | | | |
+ | +---------------------------------------------+ | |
+ +-------------------------------------------------+---+
+
+ So we create a horizontal layout, with the nav bar on the right,
+ and the left part is the normal layout below without the nav bar at
+ the bottom
+ */
+ LinearLayout topLayout = new LinearLayout(context);
+ mViewRoot = topLayout;
+ topLayout.setOrientation(LinearLayout.HORIZONTAL);
+
+ try {
+ NavigationBar navigationBar = new NavigationBar(context,
+ hardwareConfig.getDensity(), LinearLayout.VERTICAL);
+ navigationBar.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ mNavigationBarSize,
+ LayoutParams.MATCH_PARENT));
+ topLayout.addView(navigationBar);
+ } catch (XmlPullParserException e) {
+
+ }
+ }
+
+ /*
+ * we're creating the following layout
+ *
+ +-------------------------------------------------+
+ | Status bar (always) |
+ +-------------------------------------------------+
+ | (Layout with background drawable) |
+ | +---------------------------------------------+ |
+ | | Title/Action bar (optional) | |
+ | +---------------------------------------------+ |
+ | | Content, vertical extending | |
+ | | | |
+ | +---------------------------------------------+ |
+ +-------------------------------------------------+
+ | Navigation bar for soft buttons, maybe see above|
+ +-------------------------------------------------+
+
+ */
+
+ LinearLayout topLayout = new LinearLayout(context);
+ topLayout.setOrientation(LinearLayout.VERTICAL);
+ // if we don't already have a view root this is it
+ if (mViewRoot == null) {
+ mViewRoot = topLayout;
+ } else {
+ LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ layoutParams.weight = 1;
+ topLayout.setLayoutParams(layoutParams);
+
+ // this is the case of soft buttons + vertical bar.
+ // this top layout is the first layout in the horizontal layout. see above)
+ mViewRoot.addView(topLayout, 0);
+ }
+
+ if (mStatusBarSize > 0) {
+ // system bar
+ try {
+ StatusBar systemBar = new StatusBar(context, hardwareConfig.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,
+ hardwareConfig.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,
+ hardwareConfig.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 (mNavigationBarOrientation == LinearLayout.HORIZONTAL &&
+ mNavigationBarSize > 0) {
+ // system bar
+ try {
+ NavigationBar navigationBar = new NavigationBar(context,
+ hardwareConfig.getDensity(), LinearLayout.HORIZONTAL);
+ navigationBar.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, mNavigationBarSize));
+ topLayout.addView(navigationBar);
+ } 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);
+
+ // done with the parser, pop it.
+ context.popParser();
+
+ Fragment_Delegate.setProjectCallback(null);
+
+ // set the AttachInfo on the root view.
+ AttachInfo_Accessor.setAttachInfo(mViewRoot);
+
+ // 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.setBackground(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();
+ }
+
+ RenderingMode renderingMode = params.getRenderingMode();
+ HardwareConfig hardwareConfig = params.getHardwareConfig();
+
+ // only do the screen measure when needed.
+ boolean newRenderSize = false;
+ if (mMeasuredScreenWidth == -1) {
+ newRenderSize = true;
+ mMeasuredScreenWidth = hardwareConfig.getScreenWidth();
+ mMeasuredScreenHeight = hardwareConfig.getScreenHeight();
+
+ if (renderingMode != RenderingMode.NORMAL) {
+ int widthMeasureSpecMode = renderingMode.isHorizExpand() ?
+ MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
+ : MeasureSpec.EXACTLY;
+ int heightMeasureSpecMode = renderingMode.isVertExpand() ?
+ MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
+ : MeasureSpec.EXACTLY;
+
+ // We used to compare the measured size of the content to the screen size but
+ // this does not work anymore due to the 2 following issues:
+ // - If the content is in a decor (system bar, title/action bar), the root view
+ // will not resize even with the UNSPECIFIED because of the embedded layout.
+ // - If there is no decor, but a dialog frame, then the dialog padding prevents
+ // comparing the size of the content to the screen frame (as it would not
+ // take into account the dialog padding).
+
+ // The solution is to first get the content size in a normal rendering, inside
+ // the decor or the dialog padding.
+ // Then measure only the content with UNSPECIFIED to see the size difference
+ // and apply this to the screen size.
+
+ // first measure the full layout, with EXACTLY to get the size of the
+ // content as it is inside the decor/dialog
+ Pair<Integer, Integer> exactMeasure = measureView(
+ mViewRoot, mContentRoot.getChildAt(0),
+ mMeasuredScreenWidth, MeasureSpec.EXACTLY,
+ mMeasuredScreenHeight, MeasureSpec.EXACTLY);
+
+ // now measure the content only using UNSPECIFIED (where applicable, based on
+ // the rendering mode). This will give us the size the content needs.
+ Pair<Integer, Integer> result = measureView(
+ mContentRoot, mContentRoot.getChildAt(0),
+ mMeasuredScreenWidth, widthMeasureSpecMode,
+ mMeasuredScreenHeight, heightMeasureSpecMode);
+
+ // now look at the difference and add what is needed.
+ if (renderingMode.isHorizExpand()) {
+ int measuredWidth = exactMeasure.getFirst();
+ int neededWidth = result.getFirst();
+ if (neededWidth > measuredWidth) {
+ mMeasuredScreenWidth += neededWidth - measuredWidth;
+ }
+ }
+
+ if (renderingMode.isVertExpand()) {
+ int measuredHeight = exactMeasure.getSecond();
+ int neededHeight = result.getSecond();
+ if (neededHeight > measuredHeight) {
+ mMeasuredScreenHeight += neededHeight - measuredHeight;
+ }
+ }
+ }
+ }
+
+ // measure again with the size we need
+ // This must always be done before the call to layout
+ measureView(mViewRoot, null /*measuredView*/,
+ mMeasuredScreenWidth, MeasureSpec.EXACTLY,
+ mMeasuredScreenHeight, MeasureSpec.EXACTLY);
+
+ // 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 {
+ AttachInfo_Accessor.dispatchOnPreDraw(mViewRoot);
+
+ // 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*/, hardwareConfig.getDensity());
+
+ // create a Canvas around the Android bitmap
+ mCanvas = new Canvas(bitmap);
+ mCanvas.setDensity(hardwareConfig.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, params.getExtendedViewInfoMode());
+
+ // 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);
+ }
+ }
+
+ /**
+ * Executes {@link View#measure(int, int)} on a given view with the given parameters (used
+ * to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}.
+ *
+ * if <var>measuredView</var> is non null, the method returns a {@link Pair} of (width, height)
+ * for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}).
+ *
+ * @param viewToMeasure the view on which to execute measure().
+ * @param measuredView if non null, the view to query for its measured width/height.
+ * @param width the width to use in the MeasureSpec.
+ * @param widthMode the MeasureSpec mode to use for the width.
+ * @param height the height to use in the MeasureSpec.
+ * @param heightMode the MeasureSpec mode to use for the height.
+ * @return the measured width/height if measuredView is non-null, null otherwise.
+ */
+ private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
+ int width, int widthMode, int height, int heightMode) {
+ int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
+ int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode);
+ viewToMeasure.measure(w_spec, h_spec);
+
+ if (measuredView != null) {
+ return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight());
+ }
+
+ return null;
+ }
+
+ /**
+ * 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;
+
+ @Override
+ public void startTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType) {
+ if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
+ mChangeDisappearingCount++;
+ }
+ }
+
+ @Override
+ 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",
+ true /*isFrameworkAttr*/);
+ if (mWindowBackground != null) {
+ mWindowBackground = resources.resolveResValue(mWindowBackground);
+ }
+ }
+ }
+
+ private boolean hasSoftwareButtons() {
+ return getParams().getHardwareConfig().hasSoftwareButtons();
+ }
+
+ private void findStatusBar(RenderResources resources, DisplayMetrics metrics) {
+ 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("status_bar_height",
+ value.getValue(), true /*requireUnit*/);
+ 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",
+ true /*isFrameworkAttr*/);
+
+ // resolve it
+ value = resources.resolveResValue(value);
+
+ if (value != null) {
+ // get the numerical value, if available
+ TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
+ true /*requireUnit*/);
+ 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",
+ true /*isFrameworkAttr*/);
+
+ // resolve it
+ value = resources.resolveResValue(value);
+
+ if (value != null) {
+ // get the numerical value, if available
+ TypedValue typedValue = ResourceHelper.getValue("windowTitleSize",
+ value.getValue(), true /*requireUnit*/);
+ if (typedValue != null) {
+ // compute the pixel value based on the display metrics
+ mTitleBarSize = (int)typedValue.getDimension(metrics);
+ }
+ }
+ }
+
+ }
+ }
+
+ private void findNavigationBar(RenderResources resources, DisplayMetrics metrics) {
+ if (hasSoftwareButtons() && mWindowIsFloating == false) {
+
+ // default value
+ mNavigationBarSize = 48; // ??
+
+ HardwareConfig hardwareConfig = getParams().getHardwareConfig();
+
+ boolean barOnBottom = true;
+
+ if (hardwareConfig.getOrientation() == ScreenOrientation.LANDSCAPE) {
+ // compute the dp of the screen.
+ int shortSize = hardwareConfig.getScreenHeight();
+
+ // compute in dp
+ int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / hardwareConfig.getDensity().getDpiValue();
+
+ if (shortSizeDp < 600) {
+ // 0-599dp: "phone" UI with bar on the side
+ barOnBottom = false;
+ } else {
+ // 600+dp: "tablet" UI with bar on the bottom
+ barOnBottom = true;
+ }
+ }
+
+ if (barOnBottom) {
+ mNavigationBarOrientation = LinearLayout.HORIZONTAL;
+ } else {
+ mNavigationBarOrientation = LinearLayout.VERTICAL;
+ }
+
+ // get the real value
+ ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
+ barOnBottom ? "navigation_bar_height" : "navigation_bar_width");
+
+ if (value != null) {
+ TypedValue typedValue = ResourceHelper.getValue("navigation_bar_height",
+ value.getValue(), true /*requireUnit*/);
+ if (typedValue != null) {
+ // compute the pixel value based on the display metrics
+ mNavigationBarSize = (int)typedValue.getDimension(metrics);
+ }
+ }
+ }
+ }
+
+ /**
+ * Looks for a attribute in the current theme. The attribute is in the android
+ * namespace.
+ *
+ * @param resources the render resources
+ * @param name the name of the attribute
+ * @param defaultValue the default value.
+ * @return the value of the attribute or the default one if not found.
+ */
+ private boolean getBooleanThemeValue(RenderResources resources,
+ String name, boolean defaultValue) {
+
+ // get the title bar flag from the current theme.
+ ResourceValue value = resources.findItemInTheme(name, true /*isFrameworkAttr*/);
+
+ // 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 AdapterView<?>) {
+ // get the view ID.
+ int id = view.getId();
+
+ BridgeContext context = getContext();
+
+ // get a ResourceReference from the integer ID.
+ ResourceReference listRef = context.resolveId(id);
+
+ if (listRef != null) {
+ SessionParams params = getParams();
+ AdapterBinding binding = params.getAdapterBindings().get(listRef);
+
+ // if there was no adapter binding, trying to get it from the call back.
+ if (binding == null) {
+ binding = params.getProjectCallback().getAdapterBinding(listRef,
+ context.getViewKey(view), view);
+ }
+
+ if (binding != null) {
+
+ if (view instanceof AbsListView) {
+ if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
+ view instanceof ListView) {
+ ListView list = (ListView) view;
+
+ boolean skipCallbackParser = false;
+
+ int count = binding.getHeaderCount();
+ for (int i = 0 ; i < count ; i++) {
+ Pair<View, Boolean> pair = context.inflateView(
+ binding.getHeaderAt(i),
+ list, false /*attachToRoot*/, skipCallbackParser);
+ if (pair.getFirst() != null) {
+ list.addHeaderView(pair.getFirst());
+ }
+
+ skipCallbackParser |= pair.getSecond();
+ }
+
+ count = binding.getFooterCount();
+ for (int i = 0 ; i < count ; i++) {
+ Pair<View, Boolean> pair = context.inflateView(
+ binding.getFooterAt(i),
+ list, false /*attachToRoot*/, skipCallbackParser);
+ if (pair.getFirst() != null) {
+ list.addFooterView(pair.getFirst());
+ }
+
+ skipCallbackParser |= pair.getSecond();
+ }
+ }
+
+ if (view instanceof ExpandableListView) {
+ ((ExpandableListView) view).setAdapter(
+ new FakeExpandableAdapter(
+ listRef, binding, params.getProjectCallback()));
+ } else {
+ ((AbsListView) view).setAdapter(
+ new FakeAdapter(
+ listRef, binding, params.getProjectCallback()));
+ }
+ } else if (view instanceof AbsSpinner) {
+ ((AbsSpinner) view).setAdapter(
+ new FakeAdapter(
+ listRef, binding, params.getProjectCallback()));
+ }
+ }
+ }
+ } 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() {
+ @Override
+ 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, boolean setExtendedInfo) {
+ if (view == null) {
+ return null;
+ }
+
+ // adjust the offset to this view.
+ offset += view.getTop();
+
+ if (view == mContentRoot) {
+ return visitAllChildren(mContentRoot, offset, setExtendedInfo);
+ }
+
+ // 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,
+ setExtendedInfo);
+ 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.
+ * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
+ */
+ private ViewInfo visit(View view, int offset, boolean setExtendedInfo) {
+ 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 (setExtendedInfo) {
+ MarginLayoutParams marginParams = null;
+ LayoutParams params = view.getLayoutParams();
+ if (params instanceof MarginLayoutParams) {
+ marginParams = (MarginLayoutParams) params;
+ }
+ result.setExtendedInfo(view.getBaseline(),
+ marginParams != null ? marginParams.leftMargin : 0,
+ marginParams != null ? marginParams.topMargin : 0,
+ marginParams != null ? marginParams.rightMargin : 0,
+ marginParams != null ? marginParams.bottomMargin : 0);
+ }
+
+ if (view instanceof ViewGroup) {
+ ViewGroup group = ((ViewGroup) view);
+ result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo));
+ }
+
+ 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.
+ * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
+ */
+ private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
+ boolean setExtendedInfo) {
+ 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, setExtendedInfo));
+ }
+ 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/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
new file mode 100644
index 0000000..6dcb693
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -0,0 +1,493 @@
+/*
+ * 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.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.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.FileInputStream;
+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 conversion method used in handling android resources.
+ */
+public final class ResourceHelper {
+
+ private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)");
+ private final static float[] sFloatOut = new float[1];
+
+ private final static TypedValue mValue = new TypedValue();
+
+ /**
+ * Returns the color value represented by the given string value
+ * @param value the color value
+ * @return the color as an int
+ * @throw NumberFormatException if the conversion failed.
+ */
+ public static int getColor(String value) {
+ if (value != null) {
+ if (value.startsWith("#") == false) {
+ 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(String.format(
+ "Color value '%s' is too long. Format is either" +
+ "#AARRGGBB, #RRGGBB, #RGB, or #ARGB",
+ value));
+ }
+
+ if (value.length() == 3) { // RGB format
+ char[] color = new char[8];
+ color[0] = color[1] = 'F';
+ color[2] = color[3] = value.charAt(0);
+ color[4] = color[5] = value.charAt(1);
+ color[6] = color[7] = value.charAt(2);
+ value = new String(color);
+ } else if (value.length() == 4) { // ARGB format
+ char[] color = new char[8];
+ color[0] = color[1] = value.charAt(0);
+ color[2] = color[3] = value.charAt(1);
+ color[4] = color[5] = value.charAt(2);
+ color[6] = color[7] = value.charAt(3);
+ value = new String(color);
+ } else if (value.length() == 6) {
+ value = "FF" + value;
+ }
+
+ // this is a RRGGBB or AARRGGBB value
+
+ // Integer.parseInt will fail to parse strings like "ff191919", so we use
+ // a Long, but cast the result back into an int, since we know that we're only
+ // dealing with 32 bit values.
+ return (int)Long.parseLong(value, 16);
+ }
+
+ throw new NumberFormatException();
+ }
+
+ public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) {
+ String value = resValue.getValue();
+ if (value != null && RenderResources.REFERENCE_NULL.equals(value) == false) {
+ // 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
+ XmlPullParser parser = ParserFactory.create(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 the current context
+ */
+ 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()) {
+ 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 block parser for the file
+ File f = new File(stringValue);
+ if (f.isFile()) {
+ try {
+ // let the framework inflate the Drawable from the XML file.
+ XmlPullParser parser = ParserFactory.create(f);
+
+ 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;
+ } else {
+ File bmpFile = new File(stringValue);
+ if (bmpFile.isFile()) {
+ try {
+ Bitmap bitmap = Bridge.getCachedBitmap(stringValue,
+ value.isFramework() ? null : context.getProjectKey());
+
+ if (bitmap == null) {
+ bitmap = Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/,
+ density);
+ Bridge.setCachedBitmap(stringValue, bitmap,
+ value.isFramework() ? null : context.getProjectKey());
+ }
+
+ return new BitmapDrawable(context.getResources(), bitmap);
+ } catch (IOException e) {
+ // we'll return null below
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed lot load " + bmpFile.getAbsolutePath(), e, null /*data*/);
+ }
+ } else {
+ // attempt to get a color from the value
+ try {
+ int color = getColor(stringValue);
+ return new ColorDrawable(color);
+ } catch (NumberFormatException e) {
+ // we'll return null below.
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Failed to convert " + stringValue + " into a drawable", e,
+ null /*data*/);
+ }
+ }
+ }
+
+ 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
+
+ private static final class UnitEntry {
+ String name;
+ int type;
+ int unit;
+ float scale;
+
+ UnitEntry(String name, int type, int unit, float scale) {
+ this.name = name;
+ this.type = type;
+ this.unit = unit;
+ this.scale = scale;
+ }
+ }
+
+ private final static UnitEntry[] sUnitNames = new UnitEntry[] {
+ new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f),
+ new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
+ new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
+ new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f),
+ new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f),
+ new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f),
+ new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f),
+ new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100),
+ new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100),
+ };
+
+ /**
+ * Returns the raw value from the given attribute float-type value string.
+ * This object is only valid until the next call on to {@link ResourceHelper}.
+ */
+ public static TypedValue getValue(String attribute, String value, boolean requireUnit) {
+ if (parseFloatAttribute(attribute, value, mValue, requireUnit)) {
+ return mValue;
+ }
+
+ return null;
+ }
+
+ /**
+ * Parse a float attribute and return the parsed value into a given TypedValue.
+ * @param attribute the name of the attribute. Can be null if <var>requireUnit</var> is false.
+ * @param value the string value of the attribute
+ * @param outValue the TypedValue to receive the parsed value
+ * @param requireUnit whether the value is expected to contain a unit.
+ * @return true if success.
+ */
+ public static boolean parseFloatAttribute(String attribute, String value,
+ TypedValue outValue, boolean requireUnit) {
+ assert requireUnit == false || attribute != null;
+
+ // remove the space before and after
+ value = value.trim();
+ int len = value.length();
+
+ if (len <= 0) {
+ return false;
+ }
+
+ // check that there's no non ascii characters.
+ char[] buf = value.toCharArray();
+ for (int i = 0 ; i < len ; i++) {
+ if (buf[i] > 255) {
+ return false;
+ }
+ }
+
+ // check the first character
+ if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.' && buf[0] != '-') {
+ return false;
+ }
+
+ // now look for the string that is after the float...
+ Matcher m = sFloatPattern.matcher(value);
+ if (m.matches()) {
+ String f_str = m.group(1);
+ String end = m.group(2);
+
+ float f;
+ try {
+ f = Float.parseFloat(f_str);
+ } catch (NumberFormatException e) {
+ // this shouldn't happen with the regexp above.
+ return false;
+ }
+
+ if (end.length() > 0 && end.charAt(0) != ' ') {
+ // Might be a unit...
+ if (parseUnit(end, outValue, sFloatOut)) {
+ computeTypedValue(outValue, f, sFloatOut[0]);
+ return true;
+ }
+ return false;
+ }
+
+ // make sure it's only spaces at the end.
+ end = end.trim();
+
+ if (end.length() == 0) {
+ if (outValue != null) {
+ if (requireUnit == false) {
+ outValue.type = TypedValue.TYPE_FLOAT;
+ outValue.data = Float.floatToIntBits(f);
+ } else {
+ // no unit when required? Use dp and out an error.
+ applyUnit(sUnitNames[1], outValue, sFloatOut);
+ computeTypedValue(outValue, f, sFloatOut[0]);
+
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
+ String.format(
+ "Dimension \"%1$s\" in attribute \"%2$s\" is missing unit!",
+ value, attribute),
+ null);
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static void computeTypedValue(TypedValue outValue, float value, float scale) {
+ value *= scale;
+ boolean neg = value < 0;
+ if (neg) {
+ value = -value;
+ }
+ long bits = (long)(value*(1<<23)+.5f);
+ int radix;
+ int shift;
+ if ((bits&0x7fffff) == 0) {
+ // Always use 23p0 if there is no fraction, just to make
+ // things easier to read.
+ radix = TypedValue.COMPLEX_RADIX_23p0;
+ shift = 23;
+ } else if ((bits&0xffffffffff800000L) == 0) {
+ // Magnitude is zero -- can fit in 0 bits of precision.
+ radix = TypedValue.COMPLEX_RADIX_0p23;
+ shift = 0;
+ } else if ((bits&0xffffffff80000000L) == 0) {
+ // Magnitude can fit in 8 bits of precision.
+ radix = TypedValue.COMPLEX_RADIX_8p15;
+ shift = 8;
+ } else if ((bits&0xffffff8000000000L) == 0) {
+ // Magnitude can fit in 16 bits of precision.
+ radix = TypedValue.COMPLEX_RADIX_16p7;
+ shift = 16;
+ } else {
+ // Magnitude needs entire range, so no fractional part.
+ radix = TypedValue.COMPLEX_RADIX_23p0;
+ shift = 23;
+ }
+ int mantissa = (int)(
+ (bits>>shift) & TypedValue.COMPLEX_MANTISSA_MASK);
+ if (neg) {
+ mantissa = (-mantissa) & TypedValue.COMPLEX_MANTISSA_MASK;
+ }
+ outValue.data |=
+ (radix<<TypedValue.COMPLEX_RADIX_SHIFT)
+ | (mantissa<<TypedValue.COMPLEX_MANTISSA_SHIFT);
+ }
+
+ private static boolean parseUnit(String str, TypedValue outValue, float[] outScale) {
+ str = str.trim();
+
+ for (UnitEntry unit : sUnitNames) {
+ if (unit.name.equals(str)) {
+ applyUnit(unit, outValue, outScale);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static void applyUnit(UnitEntry unit, TypedValue outValue, float[] outScale) {
+ outValue.type = unit.type;
+ outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT;
+ outScale[0] = unit.scale;
+ }
+}
+
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/impl/binding/BaseAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java
new file mode 100644
index 0000000..e0414fe
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java
@@ -0,0 +1,247 @@
+/*
+ * 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.binding;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.DataBindingItem;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.util.Pair;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.Checkable;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Base adapter to do fake data binding in {@link AdapterView} objects.
+ */
+public class BaseAdapter {
+
+ /**
+ * This is the items provided by the adapter. They are dynamically generated.
+ */
+ protected final static class AdapterItem {
+ private final DataBindingItem mItem;
+ private final int mType;
+ private final int mFullPosition;
+ private final int mPositionPerType;
+ private List<AdapterItem> mChildren;
+
+ protected AdapterItem(DataBindingItem item, int type, int fullPosition,
+ int positionPerType) {
+ mItem = item;
+ mType = type;
+ mFullPosition = fullPosition;
+ mPositionPerType = positionPerType;
+ }
+
+ void addChild(AdapterItem child) {
+ if (mChildren == null) {
+ mChildren = new ArrayList<AdapterItem>();
+ }
+
+ mChildren.add(child);
+ }
+
+ List<AdapterItem> getChildren() {
+ if (mChildren != null) {
+ return mChildren;
+ }
+
+ return Collections.emptyList();
+ }
+
+ int getType() {
+ return mType;
+ }
+
+ int getFullPosition() {
+ return mFullPosition;
+ }
+
+ int getPositionPerType() {
+ return mPositionPerType;
+ }
+
+ DataBindingItem getDataBindingItem() {
+ return mItem;
+ }
+ }
+
+ private final AdapterBinding mBinding;
+ private final IProjectCallback mCallback;
+ private final ResourceReference mAdapterRef;
+ private boolean mSkipCallbackParser = false;
+
+ protected final List<AdapterItem> mItems = new ArrayList<AdapterItem>();
+
+ protected BaseAdapter(ResourceReference adapterRef, AdapterBinding binding,
+ IProjectCallback callback) {
+ mAdapterRef = adapterRef;
+ mBinding = binding;
+ mCallback = callback;
+ }
+
+ // ------- Some Adapter method used by all children classes.
+
+ public boolean areAllItemsEnabled() {
+ return true;
+ }
+
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public boolean isEmpty() {
+ return mItems.size() == 0;
+ }
+
+ public void registerDataSetObserver(DataSetObserver observer) {
+ // pass
+ }
+
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ // pass
+ }
+
+ // -------
+
+
+ protected AdapterBinding getBinding() {
+ return mBinding;
+ }
+
+ protected View getView(AdapterItem item, AdapterItem parentItem, View convertView,
+ ViewGroup parent) {
+ // we don't care about recycling here because we never scroll.
+ DataBindingItem dataBindingItem = item.getDataBindingItem();
+
+ BridgeContext context = RenderAction.getCurrentContext();
+
+ Pair<View, Boolean> pair = context.inflateView(dataBindingItem.getViewReference(),
+ parent, false /*attachToRoot*/, mSkipCallbackParser);
+
+ View view = pair.getFirst();
+ mSkipCallbackParser |= pair.getSecond();
+
+ if (view != null) {
+ fillView(context, view, item, parentItem);
+ } else {
+ // create a text view to display an error.
+ TextView tv = new TextView(context);
+ tv.setText("Unable to find layout: " + dataBindingItem.getViewReference().getName());
+ view = tv;
+ }
+
+ return view;
+ }
+
+ private void fillView(BridgeContext context, View view, AdapterItem item,
+ AdapterItem parentItem) {
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0 ; i < count ; i++) {
+ fillView(context, group.getChildAt(i), item, parentItem);
+ }
+ } else {
+ int id = view.getId();
+ if (id != 0) {
+ ResourceReference resolvedRef = context.resolveId(id);
+ if (resolvedRef != null) {
+ int fullPosition = item.getFullPosition();
+ int positionPerType = item.getPositionPerType();
+ int fullParentPosition = parentItem != null ? parentItem.getFullPosition() : 0;
+ int parentPositionPerType = parentItem != null ?
+ parentItem.getPositionPerType() : 0;
+
+ if (view instanceof TextView) {
+ TextView tv = (TextView) view;
+ Object value = mCallback.getAdapterItemValue(
+ mAdapterRef, context.getViewKey(view),
+ item.getDataBindingItem().getViewReference(),
+ fullPosition, positionPerType,
+ fullParentPosition, parentPositionPerType,
+ resolvedRef, ViewAttribute.TEXT, tv.getText().toString());
+ if (value != null) {
+ if (value.getClass() != ViewAttribute.TEXT.getAttributeClass()) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
+ "Wrong Adapter Item value class for TEXT. Expected String, got %s",
+ value.getClass().getName()), null);
+ } else {
+ tv.setText((String) value);
+ }
+ }
+ }
+
+ if (view instanceof Checkable) {
+ Checkable cb = (Checkable) view;
+
+ Object value = mCallback.getAdapterItemValue(
+ mAdapterRef, context.getViewKey(view),
+ item.getDataBindingItem().getViewReference(),
+ fullPosition, positionPerType,
+ fullParentPosition, parentPositionPerType,
+ resolvedRef, ViewAttribute.IS_CHECKED, cb.isChecked());
+ if (value != null) {
+ if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
+ "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s",
+ value.getClass().getName()), null);
+ } else {
+ cb.setChecked((Boolean) value);
+ }
+ }
+ }
+
+ if (view instanceof ImageView) {
+ ImageView iv = (ImageView) view;
+
+ Object value = mCallback.getAdapterItemValue(
+ mAdapterRef, context.getViewKey(view),
+ item.getDataBindingItem().getViewReference(),
+ fullPosition, positionPerType,
+ fullParentPosition, parentPositionPerType,
+ resolvedRef, ViewAttribute.SRC, iv.getDrawable());
+ if (value != null) {
+ if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
+ "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s",
+ value.getClass().getName()), null);
+ } else {
+ // FIXME
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java
new file mode 100644
index 0000000..22570b9
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java
@@ -0,0 +1,121 @@
+/*
+ * 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.binding;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.DataBindingItem;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.ResourceReference;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.SpinnerAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fake adapter to do fake data binding in {@link AdapterView} objects for {@link ListAdapter}
+ * and {@link SpinnerAdapter}.
+ *
+ */
+public class FakeAdapter extends BaseAdapter implements ListAdapter, SpinnerAdapter {
+
+ // don't use a set because the order is important.
+ private final List<ResourceReference> mTypes = new ArrayList<ResourceReference>();
+
+ public FakeAdapter(ResourceReference adapterRef, AdapterBinding binding,
+ IProjectCallback callback) {
+ super(adapterRef, binding, callback);
+
+ final int repeatCount = getBinding().getRepeatCount();
+ final int itemCount = getBinding().getItemCount();
+
+ // Need an array to count for each type.
+ // This is likely too big, but is the max it can be.
+ int[] typeCount = new int[itemCount];
+
+ // We put several repeating sets.
+ for (int r = 0 ; r < repeatCount ; r++) {
+ // loop on the type of list items, and add however many for each type.
+ for (DataBindingItem dataBindingItem : getBinding()) {
+ ResourceReference viewRef = dataBindingItem.getViewReference();
+ int typeIndex = mTypes.indexOf(viewRef);
+ if (typeIndex == -1) {
+ typeIndex = mTypes.size();
+ mTypes.add(viewRef);
+ }
+
+ int count = dataBindingItem.getCount();
+
+ int index = typeCount[typeIndex];
+ typeCount[typeIndex] += count;
+
+ for (int k = 0 ; k < count ; k++) {
+ mItems.add(new AdapterItem(dataBindingItem, typeIndex, mItems.size(), index++));
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return true;
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mItems.get(position).getType();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // we don't care about recycling here because we never scroll.
+ AdapterItem item = mItems.get(position);
+ return getView(item, null /*parentGroup*/, convertView, parent);
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return mTypes.size();
+ }
+
+ // ---- SpinnerAdapter
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ // pass
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java
new file mode 100644
index 0000000..199e040
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java
@@ -0,0 +1,196 @@
+/*
+ * 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.binding;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.DataBindingItem;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.ResourceReference;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ExpandableListAdapter;
+import android.widget.HeterogeneousExpandableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FakeExpandableAdapter extends BaseAdapter implements ExpandableListAdapter,
+ HeterogeneousExpandableList {
+
+ // don't use a set because the order is important.
+ private final List<ResourceReference> mGroupTypes = new ArrayList<ResourceReference>();
+ private final List<ResourceReference> mChildrenTypes = new ArrayList<ResourceReference>();
+
+ public FakeExpandableAdapter(ResourceReference adapterRef, AdapterBinding binding,
+ IProjectCallback callback) {
+ super(adapterRef, binding, callback);
+
+ createItems(binding, binding.getItemCount(), binding.getRepeatCount(), mGroupTypes, 1);
+ }
+
+ private void createItems(Iterable<DataBindingItem> iterable, final int itemCount,
+ final int repeatCount, List<ResourceReference> types, int depth) {
+ // Need an array to count for each type.
+ // This is likely too big, but is the max it can be.
+ int[] typeCount = new int[itemCount];
+
+ // we put several repeating sets.
+ for (int r = 0 ; r < repeatCount ; r++) {
+ // loop on the type of list items, and add however many for each type.
+ for (DataBindingItem dataBindingItem : iterable) {
+ ResourceReference viewRef = dataBindingItem.getViewReference();
+ int typeIndex = types.indexOf(viewRef);
+ if (typeIndex == -1) {
+ typeIndex = types.size();
+ types.add(viewRef);
+ }
+
+ List<DataBindingItem> children = dataBindingItem.getChildren();
+ int count = dataBindingItem.getCount();
+
+ // if there are children, we use the count as a repeat count for the children.
+ if (children.size() > 0) {
+ count = 1;
+ }
+
+ int index = typeCount[typeIndex];
+ typeCount[typeIndex] += count;
+
+ for (int k = 0 ; k < count ; k++) {
+ AdapterItem item = new AdapterItem(dataBindingItem, typeIndex, mItems.size(),
+ index++);
+ mItems.add(item);
+
+ if (children.size() > 0) {
+ createItems(dataBindingItem, depth + 1);
+ }
+ }
+ }
+ }
+ }
+
+ private void createItems(DataBindingItem item, int depth) {
+ if (depth == 2) {
+ createItems(item, item.getChildren().size(), item.getCount(), mChildrenTypes, depth);
+ }
+ }
+
+ private AdapterItem getChildItem(int groupPosition, int childPosition) {
+ AdapterItem item = mItems.get(groupPosition);
+
+ List<AdapterItem> children = item.getChildren();
+ return children.get(childPosition);
+ }
+
+ // ---- ExpandableListAdapter
+
+ @Override
+ public int getGroupCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public int getChildrenCount(int groupPosition) {
+ AdapterItem item = mItems.get(groupPosition);
+ return item.getChildren().size();
+ }
+
+ @Override
+ public Object getGroup(int groupPosition) {
+ return mItems.get(groupPosition);
+ }
+
+ @Override
+ public Object getChild(int groupPosition, int childPosition) {
+ return getChildItem(groupPosition, childPosition);
+ }
+
+ @Override
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+ ViewGroup parent) {
+ // we don't care about recycling here because we never scroll.
+ AdapterItem item = mItems.get(groupPosition);
+ return getView(item, null /*parentItem*/, convertView, parent);
+ }
+
+ @Override
+ public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+ View convertView, ViewGroup parent) {
+ // we don't care about recycling here because we never scroll.
+ AdapterItem parentItem = mItems.get(groupPosition);
+ AdapterItem item = getChildItem(groupPosition, childPosition);
+ return getView(item, parentItem, convertView, parent);
+ }
+
+ @Override
+ public long getGroupId(int groupPosition) {
+ return groupPosition;
+ }
+
+ @Override
+ public long getChildId(int groupPosition, int childPosition) {
+ return childPosition;
+ }
+
+ @Override
+ public long getCombinedGroupId(long groupId) {
+ return groupId << 16 | 0x0000FFFF;
+ }
+
+ @Override
+ public long getCombinedChildId(long groupId, long childId) {
+ return groupId << 16 | childId;
+ }
+
+ @Override
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return true;
+ }
+
+ @Override
+ public void onGroupCollapsed(int groupPosition) {
+ // pass
+ }
+
+ @Override
+ public void onGroupExpanded(int groupPosition) {
+ // pass
+ }
+
+ // ---- HeterogeneousExpandableList
+
+ @Override
+ public int getChildType(int groupPosition, int childPosition) {
+ return getChildItem(groupPosition, childPosition).getType();
+ }
+
+ @Override
+ public int getChildTypeCount() {
+ return mChildrenTypes.size();
+ }
+
+ @Override
+ public int getGroupType(int groupPosition) {
+ return mItems.get(groupPosition).getType();
+ }
+
+ @Override
+ public int getGroupTypeCount() {
+ return mGroupTypes.size();
+ }
+}
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/DynamicIdMap.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
new file mode 100644
index 0000000..a1fae95
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 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.resources.ResourceType;
+import com.android.util.Pair;
+
+import android.util.SparseArray;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DynamicIdMap {
+
+ private final Map<Pair<ResourceType, String>, Integer> mDynamicIds = new HashMap<Pair<ResourceType, String>, Integer>();
+ private final SparseArray<Pair<ResourceType, String>> mRevDynamicIds = new SparseArray<Pair<ResourceType, String>>();
+ private int mDynamicSeed;
+
+ public DynamicIdMap(int seed) {
+ mDynamicSeed = seed;
+ }
+
+ public void reset(int seed) {
+ mDynamicIds.clear();
+ mRevDynamicIds.clear();
+ mDynamicSeed = seed;
+ }
+
+ /**
+ * Returns a dynamic integer for the given resource type/name, creating it if it doesn't
+ * already exist.
+ *
+ * @param type the type of the resource
+ * @param name the name of the resource
+ * @return an integer.
+ */
+ public Integer getId(ResourceType type, String name) {
+ return getId(Pair.of(type, name));
+ }
+
+ /**
+ * Returns a dynamic integer for the given resource type/name, creating it if it doesn't
+ * already exist.
+ *
+ * @param resource the type/name of the resource
+ * @return an integer.
+ */
+ public Integer getId(Pair<ResourceType, String> resource) {
+ Integer value = mDynamicIds.get(resource);
+ if (value == null) {
+ value = Integer.valueOf(++mDynamicSeed);
+ mDynamicIds.put(resource, value);
+ mRevDynamicIds.put(value, resource);
+ }
+
+ return value;
+ }
+
+ public Pair<ResourceType, String> resolveId(int id) {
+ return mRevDynamicIds.get(id);
+ }
+}
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/com/google/android/maps/MapView.java b/tools/layoutlib/bridge/src/com/google/android/maps/MapView.java
new file mode 100644
index 0000000..6d013bb
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/google/android/maps/MapView.java
@@ -0,0 +1,121 @@
+/*
+ * 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.google.android.maps;
+
+import com.android.layoutlib.bridge.MockView;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Mock version of the MapView.
+ * Only non override public methods from the real MapView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ *
+ * TODO: generate automatically.
+ *
+ */
+public class MapView extends MockView {
+
+ /**
+ * Construct a new WebView with a Context object.
+ * @param context A Context object used to access application assets.
+ */
+ public MapView(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Construct a new WebView with layout parameters.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ */
+ public MapView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.mapViewStyle);
+ }
+
+ /**
+ * Construct a new WebView with layout parameters and a default style.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ * @param defStyle The default style resource ID.
+ */
+ public MapView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ // START FAKE PUBLIC METHODS
+
+ public void displayZoomControls(boolean takeFocus) {
+ }
+
+ public boolean canCoverCenter() {
+ return false;
+ }
+
+ public void preLoad() {
+ }
+
+ public int getZoomLevel() {
+ return 0;
+ }
+
+ public void setSatellite(boolean on) {
+ }
+
+ public boolean isSatellite() {
+ return false;
+ }
+
+ public void setTraffic(boolean on) {
+ }
+
+ public boolean isTraffic() {
+ return false;
+ }
+
+ public void setStreetView(boolean on) {
+ }
+
+ public boolean isStreetView() {
+ return false;
+ }
+
+ public int getLatitudeSpan() {
+ return 0;
+ }
+
+ public int getLongitudeSpan() {
+ return 0;
+ }
+
+ public int getMaxZoomLevel() {
+ return 0;
+ }
+
+ public void onSaveInstanceState(Bundle state) {
+ }
+
+ public void onRestoreInstanceState(Bundle state) {
+ }
+
+ public View getZoomControls() {
+ return null;
+ }
+}
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..cd4f82b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
@@ -0,0 +1,225 @@
+/*
+ * 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 getBestDateTimePattern(String skeleton, String localeName) {
+ return ""; // TODO: check what the right value should be.
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getCldrVersion() {
+ return "22.1.1"; // TODO: check what the right value should be.
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getIcuVersion() {
+ return "unknown_layoutlib";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getUnicodeVersion() {
+ return "5.2";
+ }
+
+ @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[] getAvailableCurrencyCodes() {
+ return new String[0];
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getCurrencyCode(String locale) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getCurrencyDisplayName(String locale, String currencyCode) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int getCurrencyFractionDigits(String currencyCode) {
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getCurrencySymbol(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 addLikelySubtags(String locale) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getScript(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.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/.classpath b/tools/layoutlib/bridge/tests/.classpath
new file mode 100644
index 0000000..2b32e09
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="res"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/layoutlib_bridge"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/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="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/layoutlib/bridge/tests/.project b/tools/layoutlib/bridge/tests/.project
new file mode 100644
index 0000000..2325eed
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>layoutlib_bridge-tests</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
new file mode 100644
index 0000000..98cade9
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/Android.mk
@@ -0,0 +1,32 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_RESOURCE_DIRS := res
+
+LOCAL_MODULE := layoutlib-tests
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := layoutlib kxml2-2.3.0 junit
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/layoutlib/bridge/tests/res/com/android/layoutlib/testdata/layout1.xml b/tools/layoutlib/bridge/tests/res/com/android/layoutlib/testdata/layout1.xml
new file mode 100644
index 0000000..b8fc947
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/com/android/layoutlib/testdata/layout1.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+>
+ <Button
+ android:id="@+id/bouton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="My Button Text"
+ >
+ </Button>
+ <View
+ android:id="@+id/surface"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ />
+ <TextView
+ android:id="@+id/status"
+ android:paddingLeft="2dip"
+ android:layout_weight="0"
+ android:background="@drawable/black"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lines="1"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="My TextView Text"
+ />
+</LinearLayout>
diff --git a/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
new file mode 100644
index 0000000..ec4edac
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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 junit.framework.TestCase;
+
+/**
+ *
+ */
+public class Matrix_DelegateTest extends TestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testIdentity() {
+ Matrix m1 = new Matrix();
+
+ assertTrue(m1.isIdentity());
+
+ m1.setValues(new float[] { 1,0,0, 0,1,0, 0,0,1 });
+ assertTrue(m1.isIdentity());
+ }
+
+ public void testCopyConstructor() {
+ Matrix m1 = new Matrix();
+ Matrix m2 = new Matrix(m1);
+
+ float[] v1 = new float[9];
+ float[] v2 = new float[9];
+ m1.getValues(v1);
+ m2.getValues(v2);
+
+ for (int i = 0 ; i < 9; i++) {
+ assertEquals(v1[i], v2[i]);
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
new file mode 100644
index 0000000..d3218db
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/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/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
new file mode 100644
index 0000000..865a008
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.android;
+
+import com.android.layoutlib.bridge.impl.ParserFactory;
+
+import org.w3c.dom.Node;
+import org.xmlpull.v1.XmlPullParser;
+
+import junit.framework.TestCase;
+
+public class BridgeXmlBlockParserTest extends TestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testXmlBlockParser() throws Exception {
+
+ XmlPullParser parser = ParserFactory.create(
+ getClass().getResourceAsStream("/com/android/layoutlib/testdata/layout1.xml"),
+ "layout1.xml");
+
+ parser = new BridgeXmlBlockParser(parser, null, false /* platformResourceFlag */);
+
+ assertEquals(XmlPullParser.START_DOCUMENT, parser.next());
+
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+ assertEquals("LinearLayout", parser.getName());
+
+ assertEquals(XmlPullParser.TEXT, parser.next());
+
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+ assertEquals("Button", parser.getName());
+ assertEquals(XmlPullParser.TEXT, parser.next());
+ assertEquals(XmlPullParser.END_TAG, parser.next());
+
+ assertEquals(XmlPullParser.TEXT, parser.next());
+
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+ assertEquals("View", parser.getName());
+ assertEquals(XmlPullParser.END_TAG, parser.next());
+
+ assertEquals(XmlPullParser.TEXT, parser.next());
+
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+ assertEquals("TextView", parser.getName());
+ assertEquals(XmlPullParser.END_TAG, parser.next());
+
+ assertEquals(XmlPullParser.TEXT, parser.next());
+
+ assertEquals(XmlPullParser.END_TAG, parser.next());
+ assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
+ }
+
+ //------------
+
+ /**
+ * Quick'n'dirty debug helper that dumps an XML structure to stdout.
+ */
+ @SuppressWarnings("unused")
+ private void dump(Node node, String prefix) {
+ Node n;
+
+ String[] types = {
+ "unknown",
+ "ELEMENT_NODE",
+ "ATTRIBUTE_NODE",
+ "TEXT_NODE",
+ "CDATA_SECTION_NODE",
+ "ENTITY_REFERENCE_NODE",
+ "ENTITY_NODE",
+ "PROCESSING_INSTRUCTION_NODE",
+ "COMMENT_NODE",
+ "DOCUMENT_NODE",
+ "DOCUMENT_TYPE_NODE",
+ "DOCUMENT_FRAGMENT_NODE",
+ "NOTATION_NODE"
+ };
+
+ String s = String.format("%s<%s> %s %s",
+ prefix,
+ types[node.getNodeType()],
+ node.getNodeName(),
+ node.getNodeValue() == null ? "" : node.getNodeValue().trim());
+
+ System.out.println(s);
+
+ n = node.getFirstChild();
+ if (n != null) {
+ dump(n, prefix + "- ");
+ }
+
+ n = node.getNextSibling();
+ if (n != null) {
+ dump(n, prefix);
+ }
+
+ }
+
+}
diff --git a/tools/layoutlib/create/.classpath b/tools/layoutlib/create/.classpath
new file mode 100644
index 0000000..dbc4cfd
--- /dev/null
+++ b/tools/layoutlib/create/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry excluding="mock_android/" kind="src" path="tests"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/layoutlib/create/.project b/tools/layoutlib/create/.project
new file mode 100644
index 0000000..e100d17
--- /dev/null
+++ b/tools/layoutlib/create/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>layoutlib_create</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/tools/layoutlib/create/.settings/README.txt b/tools/layoutlib/create/.settings/README.txt
new file mode 100644
index 0000000..9120b20
--- /dev/null
+++ b/tools/layoutlib/create/.settings/README.txt
@@ -0,0 +1,2 @@
+Copy this in eclipse project as a .settings folder at the root.
+This ensure proper compilation compliance and warning/error levels. \ No newline at end of file
diff --git a/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs b/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..5381a0e
--- /dev/null
+++ b/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,93 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled
+org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk
new file mode 100644
index 0000000..9bd48ab
--- /dev/null
+++ b/tools/layoutlib/create/Android.mk
@@ -0,0 +1,28 @@
+#
+# 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.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+
+LOCAL_JAR_MANIFEST := manifest.txt
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ asm-4.0
+
+LOCAL_MODULE := layoutlib_create
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
new file mode 100644
index 0000000..894611b
--- /dev/null
+++ b/tools/layoutlib/create/README.txt
@@ -0,0 +1,240 @@
+# Copyright (C) 2008 The Android Open Source Project
+
+
+- Description -
+---------------
+
+Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor
+to perform layout.
+
+
+- Usage -
+---------
+
+ ./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 those we don't want in the output JAR,
+- injects some new classes,
+- 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 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 a 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 for which to delegate calls.
+- 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.
+
+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.
+
+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.
+
+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.
+
+The transformed class is then fed through the DelegateClassAdapter to implement
+method delegates.
+
+Finally fields are also visited and changed from protected/private to public.
+
+
+- Method stubs
+--------------
+
+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.
+
+This strategy is now obsolete and replaced by the method delegates.
+
+
+- Strategies
+------------
+
+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.
+
+This strategy is now obsolete and replaced by the method delegates.
+See strategy 5 below.
+
+
+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.
+
+
+5- Method Delegates
+
+This strategy is used to override method implementations.
+Given a method SomeClass.MethodName(), 1 or 2 methods are generated:
+a- A copy of the original method named SomeClass.MethodName_Original().
+ The content is the original method as-is from the reader.
+ This step is omitted if the method is native, since it has no Java implementation.
+b- A brand new implementation of SomeClass.MethodName() which calls to a
+ non-existing static method named SomeClass_Delegate.MethodName().
+ The implementation of this 'delegate' method is done in layoutlib_brigde.
+
+The delegate method is a static method.
+If the original method is non-static, the delegate method receives the original 'this'
+as its first argument. If the original method is an inner non-static method, it also
+receives the inner 'this' as the second argument.
+
+
+
+- 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/manifest.txt b/tools/layoutlib/create/manifest.txt
new file mode 100644
index 0000000..238e7f9
--- /dev/null
+++ b/tools/layoutlib/create/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.tools.layoutlib.create.Main
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 100644
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 100644
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/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
new file mode 100644
index 0000000..412695f
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -0,0 +1,845 @@
+/*
+ * 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 org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Analyzes the input JAR using the ASM java bytecode manipulation library
+ * to list the desired classes and their dependencies.
+ */
+public class AsmAnalyzer {
+
+ // Note: a bunch of stuff has package-level access for unit tests. Consider it private.
+
+ /** Output logger. */
+ private final Log mLog;
+ /** The input source JAR to parse. */
+ private final List<String> mOsSourceJar;
+ /** The generator to fill with the class list and dependency list. */
+ private final AsmGenerator mGen;
+ /** Keep all classes that derive from these one (these included). */
+ private final String[] mDeriveFrom;
+ /** Glob patterns of classes to keep, e.g. "com.foo.*" */
+ private final String[] mIncludeGlobs;
+
+ /**
+ * Creates a new analyzer.
+ *
+ * @param log The log output.
+ * @param osJarPath The input source JARs to parse.
+ * @param gen The generator to fill with the class list and dependency list.
+ * @param deriveFrom Keep all classes that derive from these one (these included).
+ * @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*"
+ * ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
+ */
+ public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
+ String[] deriveFrom, String[] includeGlobs) {
+ mLog = log;
+ mGen = gen;
+ mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
+ mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
+ mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
+ }
+
+ /**
+ * Starts the analysis using parameters from the constructor.
+ * Fills the generator with classes & dependencies found.
+ */
+ public void analyze() throws IOException, LogAbortException {
+
+ AsmAnalyzer visitor = this;
+
+ Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar);
+ mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
+ mOsSourceJar.size() > 1 ? "s" : "");
+
+ Map<String, ClassReader> found = findIncludes(zipClasses);
+ Map<String, ClassReader> deps = findDeps(zipClasses, found);
+
+ if (mGen != null) {
+ mGen.setKeep(found);
+ mGen.setDeps(deps);
+ }
+ }
+
+ /**
+ * Parses a JAR file and returns a list of all classes founds using a map
+ * class name => ASM ClassReader. Class names are in the form "android.view.View".
+ */
+ Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
+ TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
+
+ for (String jarPath : jarPathList) {
+ ZipFile zip = new ZipFile(jarPath);
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ ZipEntry entry;
+ while (entries.hasMoreElements()) {
+ entry = entries.nextElement();
+ if (entry.getName().endsWith(".class")) {
+ ClassReader cr = new ClassReader(zip.getInputStream(entry));
+ String className = classReaderToClassName(cr);
+ classes.put(className, cr);
+ }
+ }
+ }
+
+ return classes;
+ }
+
+ /**
+ * Utility that returns the fully qualified binary class name for a ClassReader.
+ * E.g. it returns something like android.view.View.
+ */
+ static String classReaderToClassName(ClassReader classReader) {
+ if (classReader == null) {
+ return null;
+ } else {
+ return classReader.getClassName().replace('/', '.');
+ }
+ }
+
+ /**
+ * Utility that returns the fully qualified binary class name from a path-like FQCN.
+ * E.g. it returns android.view.View from android/view/View.
+ */
+ static String internalToBinaryClassName(String className) {
+ if (className == null) {
+ return null;
+ } else {
+ return className.replace('/', '.');
+ }
+ }
+
+ /**
+ * Process the "includes" arrays.
+ * <p/>
+ * This updates the in_out_found map.
+ */
+ Map<String, ClassReader> findIncludes(Map<String, ClassReader> zipClasses)
+ throws LogAbortException {
+ TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+
+ mLog.debug("Find classes to include.");
+
+ for (String s : mIncludeGlobs) {
+ findGlobs(s, zipClasses, found);
+ }
+ for (String s : mDeriveFrom) {
+ findClassesDerivingFrom(s, zipClasses, found);
+ }
+
+ return found;
+ }
+
+
+ /**
+ * Uses ASM to find the class reader for the given FQCN class name.
+ * If found, insert it in the in_out_found map.
+ * Returns the class reader object.
+ */
+ ClassReader findClass(String className, Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutFound) throws LogAbortException {
+ ClassReader classReader = zipClasses.get(className);
+ if (classReader == null) {
+ throw new LogAbortException("Class %s not found by ASM in %s",
+ className, mOsSourceJar);
+ }
+
+ inOutFound.put(className, classReader);
+ return classReader;
+ }
+
+ /**
+ * Insert in the inOutFound map all classes found in zipClasses that match the
+ * given glob pattern.
+ * <p/>
+ * The glob pattern is not a regexp. It only accepts the "*" keyword to mean
+ * "anything but a period". The "." and "$" characters match themselves.
+ * The "**" keyword means everything including ".".
+ * <p/>
+ * Examples:
+ * <ul>
+ * <li>com.foo.* matches all classes in the package com.foo but NOT sub-packages.
+ * <li>com.foo*.*$Event matches all internal Event classes in a com.foo*.* class.
+ * </ul>
+ */
+ void findGlobs(String globPattern, Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutFound) throws LogAbortException {
+ // transforms the glob pattern in a regexp:
+ // - escape "." with "\."
+ // - replace "*" by "[^.]*"
+ // - escape "$" with "\$"
+ // - add end-of-line match $
+ globPattern = globPattern.replaceAll("\\$", "\\\\\\$");
+ globPattern = globPattern.replaceAll("\\.", "\\\\.");
+ // prevent ** from being altered by the next rule, then process the * rule and finally
+ // the real ** rule (which is now @)
+ globPattern = globPattern.replaceAll("\\*\\*", "@");
+ globPattern = globPattern.replaceAll("\\*", "[^.]*");
+ globPattern = globPattern.replaceAll("@", ".*");
+ globPattern += "$";
+
+ Pattern regexp = Pattern.compile(globPattern);
+
+ for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
+ String class_name = entry.getKey();
+ if (regexp.matcher(class_name).matches()) {
+ findClass(class_name, zipClasses, inOutFound);
+ }
+ }
+ }
+
+ /**
+ * Checks all the classes defined in the JarClassName instance and uses BCEL to
+ * determine if they are derived from the given FQCN super class name.
+ * Inserts the super class and all the class objects found in the map.
+ */
+ void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutFound) throws LogAbortException {
+ ClassReader super_clazz = findClass(super_name, zipClasses, inOutFound);
+
+ for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
+ String className = entry.getKey();
+ if (super_name.equals(className)) {
+ continue;
+ }
+ ClassReader classReader = entry.getValue();
+ ClassReader parent_cr = classReader;
+ while (parent_cr != null) {
+ String parent_name = internalToBinaryClassName(parent_cr.getSuperName());
+ if (parent_name == null) {
+ // not found
+ break;
+ } else if (super_name.equals(parent_name)) {
+ inOutFound.put(className, classReader);
+ break;
+ }
+ parent_cr = zipClasses.get(parent_name);
+ }
+ }
+ }
+
+ /**
+ * Instantiates a new DependencyVisitor. Useful for unit tests.
+ */
+ DependencyVisitor getVisitor(Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inKeep,
+ Map<String, ClassReader> outKeep,
+ Map<String, ClassReader> inDeps,
+ Map<String, ClassReader> outDeps) {
+ return new DependencyVisitor(zipClasses, inKeep, outKeep, inDeps, outDeps);
+ }
+
+ /**
+ * Finds all dependencies for all classes in keepClasses which are also
+ * listed in zipClasses. Returns a map of all the dependencies found.
+ */
+ Map<String, ClassReader> findDeps(Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutKeepClasses) {
+
+ TreeMap<String, ClassReader> deps = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> new_deps = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> temp = new TreeMap<String, ClassReader>();
+
+ DependencyVisitor visitor = getVisitor(zipClasses,
+ inOutKeepClasses, new_keep,
+ deps, new_deps);
+
+ for (ClassReader cr : inOutKeepClasses.values()) {
+ cr.accept(visitor, 0 /* flags */);
+ }
+
+ while (new_deps.size() > 0 || new_keep.size() > 0) {
+ deps.putAll(new_deps);
+ inOutKeepClasses.putAll(new_keep);
+
+ temp.clear();
+ temp.putAll(new_deps);
+ temp.putAll(new_keep);
+ new_deps.clear();
+ new_keep.clear();
+ mLog.debug("Found %1$d to keep, %2$d dependencies.",
+ inOutKeepClasses.size(), deps.size());
+
+ for (ClassReader cr : temp.values()) {
+ cr.accept(visitor, 0 /* flags */);
+ }
+ }
+
+ mLog.info("Found %1$d classes to keep, %2$d class dependencies.",
+ inOutKeepClasses.size(), deps.size());
+
+ return deps;
+ }
+
+
+
+ // ----------------------------------
+
+ /**
+ * Visitor to collect all the type dependencies from a class.
+ */
+ public class DependencyVisitor extends ClassVisitor {
+
+ /** All classes found in the source JAR. */
+ private final Map<String, ClassReader> mZipClasses;
+ /** Classes from which dependencies are to be found. */
+ private final Map<String, ClassReader> mInKeep;
+ /** Dependencies already known. */
+ private final Map<String, ClassReader> mInDeps;
+ /** New dependencies found by this visitor. */
+ private final Map<String, ClassReader> mOutDeps;
+ /** New classes to keep as-is found by this visitor. */
+ private final Map<String, ClassReader> mOutKeep;
+
+ /**
+ * Creates a new visitor that will find all the dependencies for the visited class.
+ * Types which are already in the zipClasses, keepClasses or inDeps are not marked.
+ * New dependencies are marked in outDeps.
+ *
+ * @param zipClasses All classes found in the source JAR.
+ * @param inKeep Classes from which dependencies are to be found.
+ * @param inDeps Dependencies already known.
+ * @param outDeps New dependencies found by this visitor.
+ */
+ public DependencyVisitor(Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inKeep,
+ Map<String, ClassReader> outKeep,
+ Map<String,ClassReader> inDeps,
+ Map<String,ClassReader> outDeps) {
+ super(Opcodes.ASM4);
+ mZipClasses = zipClasses;
+ mInKeep = inKeep;
+ mOutKeep = outKeep;
+ mInDeps = inDeps;
+ mOutDeps = outDeps;
+ }
+
+ /**
+ * Considers the given class name as a dependency.
+ * If it does, add to the mOutDeps map.
+ */
+ public void considerName(String className) {
+ if (className == null) {
+ return;
+ }
+
+ className = internalToBinaryClassName(className);
+
+ // exclude classes that have already been found
+ if (mInKeep.containsKey(className) ||
+ mOutKeep.containsKey(className) ||
+ mInDeps.containsKey(className) ||
+ mOutDeps.containsKey(className)) {
+ return;
+ }
+
+ // exclude classes that are not part of the JAR file being examined
+ ClassReader cr = mZipClasses.get(className);
+ if (cr == null) {
+ return;
+ }
+
+ try {
+ // exclude classes that are part of the default JRE (the one executing this program)
+ if (getClass().getClassLoader().loadClass(className) != null) {
+ return;
+ }
+ } catch (ClassNotFoundException e) {
+ // ignore
+ }
+
+ // accept this class:
+ // - android classes are added to dependencies
+ // - non-android classes are added to the list of classes to keep as-is (they don't need
+ // to be stubbed).
+ if (className.indexOf("android") >= 0) { // TODO make configurable
+ mOutDeps.put(className, cr);
+ } else {
+ mOutKeep.put(className, cr);
+ }
+ }
+
+ /**
+ * Considers this array of names using considerName().
+ */
+ public void considerNames(String[] classNames) {
+ if (classNames != null) {
+ for (String className : classNames) {
+ considerName(className);
+ }
+ }
+ }
+
+ /**
+ * Considers this signature or type signature by invoking the {@link SignatureVisitor}
+ * on it.
+ */
+ public void considerSignature(String signature) {
+ if (signature != null) {
+ SignatureReader sr = new SignatureReader(signature);
+ // SignatureReader.accept will call accessType so we don't really have
+ // to differentiate where the signature comes from.
+ sr.accept(new MySignatureVisitor());
+ }
+ }
+
+ /**
+ * Considers this {@link Type}. For arrays, the element type is considered.
+ * If the type is an object, it's internal name is considered.
+ */
+ public void considerType(Type t) {
+ if (t != null) {
+ if (t.getSort() == Type.ARRAY) {
+ t = t.getElementType();
+ }
+ if (t.getSort() == Type.OBJECT) {
+ considerName(t.getInternalName());
+ }
+ }
+ }
+
+ /**
+ * Considers a descriptor string. The descriptor is converted to a {@link Type}
+ * and then considerType() is invoked.
+ */
+ public void considerDesc(String desc) {
+ if (desc != null) {
+ try {
+ Type t = Type.getType(desc);
+ considerType(t);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // ignore, not a valid type.
+ }
+ }
+ }
+
+
+ // ---------------------------------------------------
+ // --- ClassVisitor, FieldVisitor
+ // ---------------------------------------------------
+
+ // Visits a class header
+ @Override
+ public void visit(int version, int access, String name,
+ String signature, String superName, String[] interfaces) {
+ // signature is the signature of this class. May be null if the class is not a generic
+ // one, and does not extend or implement generic classes or interfaces.
+
+ if (signature != null) {
+ considerSignature(signature);
+ }
+
+ // superName is the internal of name of the super class (see getInternalName).
+ // For interfaces, the super class is Object. May be null but only for the Object class.
+ considerName(superName);
+
+ // interfaces is the internal names of the class's interfaces (see getInternalName).
+ // May be null.
+ considerNames(interfaces);
+ }
+
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // pass
+ }
+
+ // Visits the end of a class
+ @Override
+ public void visitEnd() {
+ // pass
+ }
+
+ private class MyFieldVisitor extends FieldVisitor {
+
+ public MyFieldVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // pass
+ }
+
+ // Visits the end of a class
+ @Override
+ public void visitEnd() {
+ // pass
+ }
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ // desc is the field's descriptor (see Type).
+ considerDesc(desc);
+
+ // signature is the field's signature. May be null if the field's type does not use
+ // generic types.
+ considerSignature(signature);
+
+ return new MyFieldVisitor();
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ // name is the internal name of an inner class (see getInternalName).
+ considerName(name);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ // desc is the method's descriptor (see Type).
+ considerDesc(desc);
+ // signature is the method's signature. May be null if the method parameters, return
+ // type and exceptions do not use generic types.
+ considerSignature(signature);
+
+ return new MyMethodVisitor();
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ // pass
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ // pass
+ }
+
+
+ // ---------------------------------------------------
+ // --- MethodVisitor
+ // ---------------------------------------------------
+
+ private class MyMethodVisitor extends MethodVisitor {
+
+ public MyMethodVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitCode() {
+ // pass
+ }
+
+ // field instruction
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ // name is the field's name.
+ considerName(name);
+ // desc is the field's descriptor (see Type).
+ considerDesc(desc);
+ }
+
+ @Override
+ public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) {
+ // pass
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ // pass -- an IINC instruction
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ // pass -- a zero operand instruction
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ // pass -- a single int operand instruction
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ // pass -- a jump instruction
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ // pass -- a label target
+ }
+
+ // instruction to load a constant from the stack
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (cst instanceof Type) {
+ considerType((Type) cst);
+ }
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ // pass
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc,
+ String signature, Label start, Label end, int index) {
+ // desc is the type descriptor of this local variable.
+ considerDesc(desc);
+ // signature is the type signature of this local variable. May be null if the local
+ // variable type does not use generic types.
+ considerSignature(signature);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ // pass -- a lookup switch instruction
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ // pass
+ }
+
+ // instruction that invokes a method
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+
+ // owner is the internal name of the method's owner class
+ considerName(owner);
+ // desc is the method's descriptor (see Type).
+ considerDesc(desc);
+ }
+
+ // instruction multianewarray, whatever that is
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+
+ // desc an array type descriptor.
+ considerDesc(desc);
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ // pass -- table switch instruction
+
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ // type is the internal name of the type of exceptions handled by the handler,
+ // or null to catch any exceptions (for "finally" blocks).
+ considerName(type);
+ }
+
+ // type instruction
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ // type is the operand of the instruction to be visited. This operand must be the
+ // internal name of an object or array class.
+ considerName(type);
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ // pass -- local variable instruction
+ }
+ }
+
+ private class MySignatureVisitor extends SignatureVisitor {
+
+ public MySignatureVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ // ---------------------------------------------------
+ // --- SignatureVisitor
+ // ---------------------------------------------------
+
+ private String mCurrentSignatureClass = null;
+
+ // Starts the visit of a signature corresponding to a class or interface type
+ @Override
+ public void visitClassType(String name) {
+ mCurrentSignatureClass = name;
+ considerName(name);
+ }
+
+ // Visits an inner class
+ @Override
+ public void visitInnerClassType(String name) {
+ if (mCurrentSignatureClass != null) {
+ mCurrentSignatureClass += "$" + name;
+ considerName(mCurrentSignatureClass);
+ }
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ // pass -- a primitive type, ignored
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ // pass
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ // pass
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ // pass
+ }
+ }
+
+
+ // ---------------------------------------------------
+ // --- AnnotationVisitor
+ // ---------------------------------------------------
+
+ private class MyAnnotationVisitor extends AnnotationVisitor {
+
+ public MyAnnotationVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ // Visits a primitive value of an annotation
+ @Override
+ public void visit(String name, Object value) {
+ // value is the actual value, whose type must be Byte, Boolean, Character, Short,
+ // Integer, Long, Float, Double, String or Type
+ if (value instanceof Type) {
+ considerType((Type) value);
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ // desc is the class descriptor of the nested annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitEnum(String name, String desc, String value) {
+ // desc is the class descriptor of the enumeration class.
+ considerDesc(desc);
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..a9ede26
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -0,0 +1,370 @@
+/*
+ * 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 org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+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.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+/**
+ * Class that generates a new JAR from a list of classes, some of which are to be kept as-is
+ * and some of which are to be stubbed partially or totally.
+ */
+public class AsmGenerator {
+
+ /** Output logger. */
+ private final Log mLog;
+ /** The path of the destination JAR to create. */
+ private final String mOsDestJar;
+ /** List of classes to inject in the final JAR from _this_ archive. */
+ private final Class<?>[] mInjectClasses;
+ /** The set of methods to stub out. */
+ private final Set<String> mStubMethods;
+ /** All classes to output as-is, except if they have native methods. */
+ private Map<String, ClassReader> mKeep;
+ /** All dependencies that must be completely stubbed. */
+ private Map<String, ClassReader> mDeps;
+ /** Counter of number of classes renamed during transform. */
+ private int mRenameCount;
+ /** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */
+ private final HashMap<String, String> mRenameClasses;
+ /** FQCN Names of "old" classes that were NOT renamed. This starts with the full list of
+ * 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 => 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 createInfo Creation parameters. Must not be null.
+ */
+ public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
+ mLog = log;
+ mOsDestJar = osDestJar;
+ 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>();
+ 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 "."
+ String oldFqcn = binaryToInternalClassName(renameClasses[i]);
+ String newFqcn = binaryToInternalClassName(renameClasses[i + 1]);
+ 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>>();
+ 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);
+ }
+
+ 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/>
+ * The names are "internal class names" rather than FQCN, i.e. they use "/" instead "."
+ * as package separators.
+ */
+ public Set<String> getClassesNotRenamed() {
+ return mClassesNotRenamed;
+ }
+
+ /**
+ * Utility that returns the internal ASM class name from a fully qualified binary class
+ * name. E.g. it returns android/view/View from android.view.View.
+ */
+ String binaryToInternalClassName(String className) {
+ if (className == null) {
+ return null;
+ } else {
+ return className.replace('.', '/');
+ }
+ }
+
+ /** Sets the map of classes to output as-is, except if they have native methods */
+ public void setKeep(Map<String, ClassReader> keep) {
+ mKeep = keep;
+ }
+
+ /** Sets the map of dependencies that must be completely stubbed */
+ 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;
+ }
+
+ /** 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);
+ ClassReader cr = new ClassReader(is);
+ byte[] b = transform(cr, true /* stubNativesOnly */);
+ 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 */);
+ String name = classNameToEntryPath(transformName(cr.getClassName()));
+ all.put(name, b);
+ }
+
+ for (Entry<String, ClassReader> entry : mKeep.entrySet()) {
+ ClassReader cr = entry.getValue();
+ byte[] b = transform(cr, true /* stubNativesOnly */);
+ String name = classNameToEntryPath(transformName(cr.getClassName()));
+ all.put(name, b);
+ }
+
+ mLog.info("# deps classes: %d", mDeps.size());
+ mLog.info("# keep classes: %d", mKeep.size());
+ mLog.info("# renamed : %d", mRenameCount);
+
+ createJar(new FileOutputStream(mOsDestJar), all);
+ mLog.info("Created JAR file %s", mOsDestJar);
+ }
+
+ /**
+ * Writes the JAR file.
+ *
+ * @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
+ */
+ void createJar(FileOutputStream outStream, Map<String,byte[]> all) throws IOException {
+ JarOutputStream jar = new JarOutputStream(outStream);
+ for (Entry<String, byte[]> entry : all.entrySet()) {
+ String name = entry.getKey();
+ JarEntry jar_entry = new JarEntry(name);
+ jar.putNextEntry(jar_entry);
+ jar.write(entry.getValue());
+ jar.closeEntry();
+ }
+ jar.flush();
+ jar.close();
+ }
+
+ /**
+ * Utility method that converts a fully qualified java name into a JAR entry path
+ * e.g. for the input "android.view.View" it returns "android/view/View.class"
+ */
+ 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"
+ */
+ private String classToEntryPath(Class<?> clazz) {
+ String name = "";
+ Class<?> parent;
+ while ((parent = clazz.getEnclosingClass()) != null) {
+ name = "$" + clazz.getSimpleName() + name;
+ clazz = parent;
+ }
+ 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) {
+ mRenameCount++;
+ // This class is being renamed, so remove it from the list of classes not renamed.
+ mClassesNotRenamed.remove(className);
+ }
+
+ mLog.debug("Transform %s%s%s%s", className,
+ newName == className ? "" : " (renamed to " + newName + ")",
+ hasNativeMethods ? " -- has natives" : "",
+ stubNativesOnly ? " -- stub natives only" : "");
+
+ // 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);
+ }
+
+ 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();
+ }
+
+ /**
+ * Should this class be renamed, this returns the new name. Otherwise it returns the
+ * original name.
+ *
+ * @param className The internal ASM name of the class that may have to be renamed
+ * @return A new transformed name or the original input argument.
+ */
+ String transformName(String className) {
+ String newName = mRenameClasses.get(className);
+ if (newName != null) {
+ return newName;
+ }
+ int pos = className.indexOf('$');
+ if (pos > 0) {
+ // Is this an inner class of a renamed class?
+ String base = className.substring(0, pos);
+ newName = mRenameClasses.get(base);
+ if (newName != null) {
+ return newName + className.substring(pos);
+ }
+ }
+
+ return className;
+ }
+
+ /**
+ * Returns true if a class has any native methods.
+ */
+ boolean hasNativeMethods(ClassReader cr) {
+ ClassHasNativeVisitor cv = new ClassHasNativeVisitor();
+ cr.accept(cv, 0 /* flags */);
+ return cv.hasNativeMethods();
+ }
+
+}
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
new file mode 100644
index 0000000..2c955fd
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
@@ -0,0 +1,102 @@
+/*
+ * 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.VisibleForTesting;
+import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Indicates if a class contains any native methods.
+ */
+public class ClassHasNativeVisitor extends ClassVisitor {
+ public ClassHasNativeVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ private boolean mHasNativeMethods = false;
+
+ public boolean hasNativeMethods() {
+ return mHasNativeMethods;
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) {
+ mHasNativeMethods = hasNativeMethods;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ // pass
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // pass
+ }
+
+ @Override
+ public void visitEnd() {
+ // pass
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName,
+ String innerName, int access) {
+ // pass
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ if ((access & Opcodes.ACC_NATIVE) != 0) {
+ setHasNativeMethods(true, name);
+ }
+ return null;
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ // pass
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ // pass
+ }
+
+}
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
new file mode 100644
index 0000000..d955040
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -0,0 +1,207 @@
+/*
+ * 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;
+
+/**
+ * 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.
+ */
+ @Override
+ 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.
+ */
+ @Override
+ 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.
+ */
+ @Override
+ 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.
+ * <p/>
+ * This usage is deprecated. Please use method 'delegates' instead.
+ */
+ @Override
+ 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.
+ */
+ @Override
+ 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.
+ */
+ @Override
+ public String[] getDeleteReturns() {
+ return DELETE_RETURNS;
+ }
+
+ //-----
+
+ /**
+ * The list of class from layoutlib_create to inject in layoutlib.
+ */
+ private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
+ OverrideMethod.class,
+ MethodListener.class,
+ MethodAdapter.class,
+ ICreateInfo.class,
+ CreateInfo.class,
+ LayoutlibDelegate.class
+ };
+
+ /**
+ * The list of methods to rewrite as delegates.
+ */
+ public 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.content.res.TypedArray#getValueAt",
+ "android.graphics.BitmapFactory#finishDecode",
+ "android.os.Handler#sendMessageAtTime",
+ "android.os.HandlerThread#run",
+ "android.os.Build#getString",
+ "android.text.format.DateFormat#is24HourFormat",
+ "android.view.Choreographer#getRefreshRate",
+ "android.view.Display#updateDisplayInfoLocked",
+ "android.view.LayoutInflater#rInflate",
+ "android.view.LayoutInflater#parseInclude",
+ "android.view.View#isInEditMode",
+ "android.view.ViewRootImpl#isInTouchMode",
+ "android.view.WindowManagerGlobal#getWindowManagerService",
+ "android.view.inputmethod.InputMethodManager#getInstance",
+ "com.android.internal.util.XmlUtils#convertValueToInt",
+ "com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
+ };
+
+ /**
+ * The list of classes on which to delegate all native methods.
+ */
+ public 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.text.AndroidBidi",
+ "android.util.FloatMath",
+ "android.view.Display",
+ "libcore.icu.ICU",
+ };
+
+ /**
+ * The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * This usage is deprecated. Please use method 'delegates' instead.
+ */
+ 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.
+ */
+ private final static String[] RENAMED_CLASSES =
+ new String[] {
+ "android.os.ServiceManager", "android.os._Original_ServiceManager",
+ "android.util.LruCache", "android.util._Original_LruCache",
+ "android.view.SurfaceView", "android.view._Original_SurfaceView",
+ "android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager",
+ "android.webkit.WebView", "android.webkit._Original_WebView",
+ "com.android.internal.policy.PolicyManager", "com.android.internal.policy._Original_PolicyManager",
+ };
+
+ /**
+ * 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.
+ */
+ private final static String[] DELETE_RETURNS =
+ new String[] {
+ 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..927be97
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -0,0 +1,133 @@
+/*
+ * 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.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 ClassVisitor {
+
+ /** Suffix added to original methods. */
+ private static final String ORIGINAL_SUFFIX = "_Original";
+ private static String CONSTRUCTOR = "<init>";
+ private static String CLASS_INIT = "<clinit>";
+
+ 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(Opcodes.ASM4, 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) {
+ // Not creating a delegate for this method, pass it as-is from the reader
+ // to the writer.
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+
+ if (useDelegate) {
+ if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) {
+ // We don't currently support generating delegates for constructors.
+ throw new UnsupportedOperationException(
+ String.format(
+ "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$
+ mClassName, name, desc));
+ }
+ }
+
+ if (isNative) {
+ // Remove native flag
+ access = access & ~Opcodes.ACC_NATIVE;
+ MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
+
+ DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
+ mLog, null /*mwOriginal*/, mwDelegate, mClassName, name, desc, isStatic);
+
+ // A native has no code to visit, so we need to generate it directly.
+ a.generateDelegateCode();
+
+ return mwDelegate;
+ }
+
+ // Given a non-native SomeClass.MethodName(), we want to generate 2 methods:
+ // - A copy of the original method named SomeClass.MethodName_Original().
+ // The content is the original method as-is from the reader.
+ // - A brand new implementation of SomeClass.MethodName() which calls to a
+ // non-existing method named SomeClass_Delegate.MethodName().
+ // The implementation of this 'delegate' method is done in layoutlib_brigde.
+
+ int accessDelegate = access;
+ // change access to public for the original one
+ if (Main.sOptions.generatePublicAccess) {
+ access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
+ access |= Opcodes.ACC_PUBLIC;
+ }
+
+ MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX,
+ desc, signature, exceptions);
+ MethodVisitor mwDelegate = super.visitMethod(accessDelegate, name,
+ desc, signature, exceptions);
+
+ DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
+ mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
+ return a;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java
new file mode 100644
index 0000000..0000b22
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java
@@ -0,0 +1,461 @@
+/*
+ * 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 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 generates delegate methods.
+ * <p/>
+ * Given a method {@code SomeClass.MethodName()}, this generates 1 or 2 methods:
+ * <ul>
+ * <li> A copy of the original method named {@code SomeClass.MethodName_Original()}.
+ * The content is the original method as-is from the reader.
+ * This step is omitted if the method is native, since it has no Java implementation.
+ * <li> A brand new implementation of {@code SomeClass.MethodName()} which calls to a
+ * non-existing method named {@code SomeClass_Delegate.MethodName()}.
+ * The implementation of this 'delegate' method is done in layoutlib_brigde.
+ * </ul>
+ * A method visitor is generally constructed to generate a single method; however
+ * here we might want to generate one or two depending on the context. To achieve
+ * that, the visitor here generates the 'original' method and acts as a no-op if
+ * no such method exists (e.g. when the original is a native method).
+ * The delegate method is generated after the {@code visitEnd} of the original method
+ * or by having the class adapter <em>directly</em> call {@link #generateDelegateCode()}
+ * for native methods.
+ * <p/>
+ * When generating the 'delegate', the implementation generates a call to a class
+ * class named <code>&lt;className&gt;_Delegate</code> with static methods matching
+ * the methods to be overridden here. The methods have the same return type.
+ * The argument type list is the same except the "this" reference is passed first
+ * for non-static methods.
+ * <p/>
+ * A new annotation is added to these 'delegate' methods so that we can easily find them
+ * for automated testing.
+ * <p/>
+ * This class isn't intended to be generic or reusable.
+ * It is called by {@link DelegateClassAdapter}, which takes care of properly initializing
+ * the two method writers for the original and the delegate class, as needed, with their
+ * expected names.
+ * <p/>
+ * The class adapter also takes care of calling {@link #generateDelegateCode()} directly for
+ * a native and use the visitor pattern for non-natives.
+ * Note that native methods have, by definition, no code so there's nothing a visitor
+ * can visit.
+ * <p/>
+ * Instances of this class are not re-usable.
+ * The class adapter creates a new instance for each method.
+ */
+class DelegateMethodAdapter2 extends MethodVisitor {
+
+ /** Suffix added to delegate classes. */
+ public static final String DELEGATE_SUFFIX = "_Delegate";
+
+ /** The parent method writer to copy of the original method.
+ * Null when dealing with a native original method. */
+ private MethodVisitor mOrgWriter;
+ /** The parent method writer to generate the delegating method. Never null. */
+ private MethodVisitor mDelWriter;
+ /** 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;
+
+ /** Array used to capture the first line number information from the original method
+ * and duplicate it in the delegate. */
+ private Object[] mDelegateLineNumber;
+
+ /**
+ * Creates a new {@link DelegateMethodAdapter2} that will transform this method
+ * into a delegate call.
+ * <p/>
+ * See {@link DelegateMethodAdapter2} for more details.
+ *
+ * @param log The logger object. Must not be null.
+ * @param mvOriginal The parent method writer to copy of the original method.
+ * Must be {@code null} when dealing with a native original method.
+ * @param mvDelegate The parent method writer to generate the delegating method.
+ * Must never be null.
+ * @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 DelegateMethodAdapter2(Log log,
+ MethodVisitor mvOriginal,
+ MethodVisitor mvDelegate,
+ String className,
+ String methodName,
+ String desc,
+ boolean isStatic) {
+ super(Opcodes.ASM4);
+ mLog = log;
+ mOrgWriter = mvOriginal;
+ mDelWriter = mvDelegate;
+ mClassName = className;
+ mMethodName = methodName;
+ mDesc = desc;
+ mIsStatic = isStatic;
+ }
+
+ /**
+ * 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 DelegateMethodAdapter2} 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 generateDelegateCode() {
+ /*
+ * 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 myMethod(int a, Object b, ArrayList<String> c) { ... }
+ *
+ * We'll want to create a body that calls a delegate method like this:
+ * TheClass_Delegate.myMethod(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.myMethod(
+ * 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 build time, they have no influence on the method name being called.
+ */
+
+ // Add our annotation
+ AnnotationVisitor aw = mDelWriter.visitAnnotation(
+ Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
+ true); // visible at runtime
+ if (aw != null) {
+ aw.visitEnd();
+ }
+
+ mDelWriter.visitCode();
+
+ if (mDelegateLineNumber != null) {
+ Object[] p = mDelegateLineNumber;
+ mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]);
+ }
+
+ ArrayList<Type> paramTypes = new ArrayList<Type>();
+ String delegateClassName = mClassName + DELEGATE_SUFFIX;
+ boolean pushedArg0 = false;
+ int maxStack = 0;
+
+ // Check if the last segment of the class name has inner an class.
+ // Right now we only support one level of inner classes.
+ Type outerType = null;
+ int slash = mClassName.lastIndexOf('/');
+ int dol = mClassName.lastIndexOf('$');
+ if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) {
+ String outerClass = mClassName.substring(0, dol);
+ outerType = Type.getObjectType(outerClass);
+
+ // Change a delegate class name to "com/foo/Outer_Inner_Delegate"
+ delegateClassName = delegateClassName.replace('$', '_');
+ }
+
+ // For an instance method (e.g. non-static), push the 'this' preceded
+ // by the 'this' of any outer class, if any.
+ if (!mIsStatic) {
+
+ if (outerType != null) {
+ // 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.
+ mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
+ mDelWriter.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
+ mDelWriter.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();
+ mDelWriter.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
+ mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC,
+ delegateClassName,
+ mMethodName,
+ desc);
+
+ Type returnType = Type.getReturnType(mDesc);
+ mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+
+ mDelWriter.visitMaxs(maxStack, maxLocals);
+ mDelWriter.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. */
+ @Override
+ public void visitCode() {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitCode();
+ }
+ }
+
+ /*
+ * visitMaxs is called just before visitEnd if there was any code to rewrite.
+ */
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitMaxs(maxStack, maxLocals);
+ }
+ }
+
+ /** End of visiting. Generate the delegating code. */
+ @Override
+ public void visitEnd() {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitEnd();
+ }
+ generateDelegateCode();
+ }
+
+ /* Writes all annotation from the original method. */
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (mOrgWriter != null) {
+ return mOrgWriter.visitAnnotation(desc, visible);
+ } else {
+ return null;
+ }
+ }
+
+ /* Writes all annotation default values from the original method. */
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ if (mOrgWriter != null) {
+ return mOrgWriter.visitAnnotationDefault();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ if (mOrgWriter != null) {
+ return mOrgWriter.visitParameterAnnotation(parameter, desc, visible);
+ } else {
+ return null;
+ }
+ }
+
+ /* Writes all attributes from the original method. */
+ @Override
+ public void visitAttribute(Attribute attr) {
+ if (mOrgWriter != null) {
+ mOrgWriter.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.
+ */
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ // Capture the first line values for the new delegate method
+ if (mDelegateLineNumber == null) {
+ mDelegateLineNumber = new Object[] { line, start };
+ }
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLineNumber(line, start);
+ }
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitInsn(opcode);
+ }
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLabel(label);
+ }
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitTryCatchBlock(start, end, handler, type);
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitMethodInsn(opcode, owner, name, desc);
+ }
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ @Override
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitFrame(type, nLocal, local, nStack, stack);
+ }
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitIincInsn(var, increment);
+ }
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitIntInsn(opcode, operand);
+ }
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitJumpInsn(opcode, label);
+ }
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLdcInsn(cst);
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitMultiANewArrayInsn(desc, dims);
+ }
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitTypeInsn(opcode, type);
+ }
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitVarInsn(opcode, var);
+ }
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
new file mode 100644
index 0000000..c988c70
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
@@ -0,0 +1,787 @@
+/*
+ * Copyright (C) 2012 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.VisibleForTesting;
+import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Analyzes the input JAR using the ASM java bytecode manipulation library
+ * to list the classes and their dependencies. A "dependency" is a class
+ * used by another class.
+ */
+public class DependencyFinder {
+
+ // Note: a bunch of stuff has package-level access for unit tests. Consider it private.
+
+ /** Output logger. */
+ private final Log mLog;
+
+ /**
+ * Creates a new analyzer.
+ *
+ * @param log The log output.
+ */
+ public DependencyFinder(Log log) {
+ mLog = log;
+ }
+
+ /**
+ * Starts the analysis using parameters from the constructor.
+ *
+ * @param osJarPath The input source JARs to parse.
+ * @return A pair: [0]: map { class FQCN => set of FQCN class dependencies }.
+ * [1]: map { missing class FQCN => set of FQCN class that uses it. }
+ */
+ public List<Map<String, Set<String>>> findDeps(List<String> osJarPath) throws IOException {
+
+ Map<String, ClassReader> zipClasses = parseZip(osJarPath);
+ mLog.info("Found %d classes in input JAR%s.",
+ zipClasses.size(),
+ osJarPath.size() > 1 ? "s" : "");
+
+ Map<String, Set<String>> deps = findClassesDeps(zipClasses);
+
+ Map<String, Set<String>> missing = findMissingClasses(deps, zipClasses.keySet());
+
+ List<Map<String, Set<String>>> result = new ArrayList<Map<String,Set<String>>>(2);
+ result.add(deps);
+ result.add(missing);
+ return result;
+ }
+
+ /**
+ * Prints dependencies to the current logger, found stuff and missing stuff.
+ */
+ public void printAllDeps(List<Map<String, Set<String>>> result) {
+ assert result.size() == 2;
+ Map<String, Set<String>> deps = result.get(0);
+ Map<String, Set<String>> missing = result.get(1);
+
+ // Print all dependences found in the format:
+ // +Found: <FQCN from zip>
+ // uses: FQCN
+
+ mLog.info("++++++ %d Entries found in source JARs", deps.size());
+ mLog.info("");
+
+ for (Entry<String, Set<String>> entry : deps.entrySet()) {
+ mLog.info( "+Found : %s", entry.getKey());
+ for (String dep : entry.getValue()) {
+ mLog.info(" uses: %s", dep);
+ }
+
+ mLog.info("");
+ }
+
+
+ // Now print all missing dependences in the format:
+ // -Missing <FQCN>:
+ // used by: <FQCN>
+
+ mLog.info("");
+ mLog.info("------ %d Entries missing from source JARs", missing.size());
+ mLog.info("");
+
+ for (Entry<String, Set<String>> entry : missing.entrySet()) {
+ mLog.info( "-Missing : %s", entry.getKey());
+ for (String dep : entry.getValue()) {
+ mLog.info(" used by: %s", dep);
+ }
+
+ mLog.info("");
+ }
+ }
+
+ /**
+ * Prints only a summary of the missing dependencies to the current logger.
+ */
+ public void printMissingDeps(List<Map<String, Set<String>>> result) {
+ assert result.size() == 2;
+ @SuppressWarnings("unused") Map<String, Set<String>> deps = result.get(0);
+ Map<String, Set<String>> missing = result.get(1);
+
+ for (String fqcn : missing.keySet()) {
+ mLog.info("%s", fqcn);
+ }
+ }
+
+ // ----------------
+
+ /**
+ * Parses a JAR file and returns a list of all classes founds using a map
+ * class name => ASM ClassReader. Class names are in the form "android.view.View".
+ */
+ Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
+ TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
+
+ for (String jarPath : jarPathList) {
+ ZipFile zip = new ZipFile(jarPath);
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ ZipEntry entry;
+ while (entries.hasMoreElements()) {
+ entry = entries.nextElement();
+ if (entry.getName().endsWith(".class")) {
+ ClassReader cr = new ClassReader(zip.getInputStream(entry));
+ String className = classReaderToClassName(cr);
+ classes.put(className, cr);
+ }
+ }
+ }
+
+ return classes;
+ }
+
+ /**
+ * Utility that returns the fully qualified binary class name for a ClassReader.
+ * E.g. it returns something like android.view.View.
+ */
+ static String classReaderToClassName(ClassReader classReader) {
+ if (classReader == null) {
+ return null;
+ } else {
+ return classReader.getClassName().replace('/', '.');
+ }
+ }
+
+ /**
+ * Utility that returns the fully qualified binary class name from a path-like FQCN.
+ * E.g. it returns android.view.View from android/view/View.
+ */
+ static String internalToBinaryClassName(String className) {
+ if (className == null) {
+ return null;
+ } else {
+ return className.replace('/', '.');
+ }
+ }
+
+ /**
+ * Finds all dependencies for all classes in keepClasses which are also
+ * listed in zipClasses. Returns a map of all the dependencies found.
+ */
+ Map<String, Set<String>> findClassesDeps(Map<String, ClassReader> zipClasses) {
+
+ // The dependencies that we'll collect.
+ // It's a map Class name => uses class names.
+ Map<String, Set<String>> dependencyMap = new TreeMap<String, Set<String>>();
+
+ DependencyVisitor visitor = getVisitor();
+
+ int count = 0;
+ try {
+ for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
+ String name = entry.getKey();
+
+ TreeSet<String> set = new TreeSet<String>();
+ dependencyMap.put(name, set);
+ visitor.setDependencySet(set);
+
+ ClassReader cr = entry.getValue();
+ cr.accept(visitor, 0 /* flags */);
+
+ visitor.setDependencySet(null);
+
+ mLog.debugNoln("Visited %d classes\r", ++count);
+ }
+ } finally {
+ mLog.debugNoln("\n");
+ }
+
+ return dependencyMap;
+ }
+
+ /**
+ * Computes which classes FQCN were found as dependencies that are NOT listed
+ * in the original JAR classes.
+ *
+ * @param deps The map { FQCN => dependencies[] } returned by {@link #findClassesDeps(Map)}.
+ * @param zipClasses The set of all classes FQCN found in the JAR files.
+ * @return A map { FQCN not found in the zipClasses => classes using it }
+ */
+ private Map<String, Set<String>> findMissingClasses(
+ Map<String, Set<String>> deps,
+ Set<String> zipClasses) {
+ Map<String, Set<String>> missing = new TreeMap<String, Set<String>>();
+
+ for (Entry<String, Set<String>> entry : deps.entrySet()) {
+ String name = entry.getKey();
+
+ for (String dep : entry.getValue()) {
+ if (!zipClasses.contains(dep)) {
+ // This dependency doesn't exist in the zip classes.
+ Set<String> set = missing.get(dep);
+ if (set == null) {
+ set = new TreeSet<String>();
+ missing.put(dep, set);
+ }
+ set.add(name);
+ }
+ }
+
+ }
+
+ return missing;
+ }
+
+
+ // ----------------------------------
+
+ /**
+ * Instantiates a new DependencyVisitor. Useful for unit tests.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ DependencyVisitor getVisitor() {
+ return new DependencyVisitor();
+ }
+
+ /**
+ * Visitor to collect all the type dependencies from a class.
+ */
+ public class DependencyVisitor extends ClassVisitor {
+
+ private Set<String> mCurrentDepSet;
+
+ /**
+ * Creates a new visitor that will find all the dependencies for the visited class.
+ */
+ public DependencyVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ /**
+ * Sets the {@link Set} where to record direct dependencies for this class.
+ * This will change before each {@link ClassReader#accept(ClassVisitor, int)} call.
+ */
+ public void setDependencySet(Set<String> set) {
+ mCurrentDepSet = set;
+ }
+
+ /**
+ * Considers the given class name as a dependency.
+ */
+ public void considerName(String className) {
+ if (className == null) {
+ return;
+ }
+
+ className = internalToBinaryClassName(className);
+
+ try {
+ // exclude classes that are part of the default JRE (the one executing this program)
+ if (getClass().getClassLoader().loadClass(className) != null) {
+ return;
+ }
+ } catch (ClassNotFoundException e) {
+ // ignore
+ }
+
+ // Add it to the dependency set for the currently visited class, as needed.
+ assert mCurrentDepSet != null;
+ if (mCurrentDepSet != null) {
+ mCurrentDepSet.add(className);
+ }
+ }
+
+ /**
+ * Considers this array of names using considerName().
+ */
+ public void considerNames(String[] classNames) {
+ if (classNames != null) {
+ for (String className : classNames) {
+ considerName(className);
+ }
+ }
+ }
+
+ /**
+ * Considers this signature or type signature by invoking the {@link SignatureVisitor}
+ * on it.
+ */
+ public void considerSignature(String signature) {
+ if (signature != null) {
+ SignatureReader sr = new SignatureReader(signature);
+ // SignatureReader.accept will call accessType so we don't really have
+ // to differentiate where the signature comes from.
+ sr.accept(new MySignatureVisitor());
+ }
+ }
+
+ /**
+ * Considers this {@link Type}. For arrays, the element type is considered.
+ * If the type is an object, it's internal name is considered.
+ */
+ public void considerType(Type t) {
+ if (t != null) {
+ if (t.getSort() == Type.ARRAY) {
+ t = t.getElementType();
+ }
+ if (t.getSort() == Type.OBJECT) {
+ considerName(t.getInternalName());
+ }
+ }
+ }
+
+ /**
+ * Considers a descriptor string. The descriptor is converted to a {@link Type}
+ * and then considerType() is invoked.
+ */
+ public boolean considerDesc(String desc) {
+ if (desc != null) {
+ try {
+ if (desc.length() > 0 && desc.charAt(0) == '(') {
+ // This is a method descriptor with arguments and a return type.
+ Type t = Type.getReturnType(desc);
+ considerType(t);
+
+ for (Type arg : Type.getArgumentTypes(desc)) {
+ considerType(arg);
+ }
+
+ } else {
+ Type t = Type.getType(desc);
+ considerType(t);
+ }
+ return true;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // ignore, not a valid type.
+ }
+ }
+ return false;
+ }
+
+
+ // ---------------------------------------------------
+ // --- ClassVisitor, FieldVisitor
+ // ---------------------------------------------------
+
+ // Visits a class header
+ @Override
+ public void visit(int version, int access, String name,
+ String signature, String superName, String[] interfaces) {
+ // signature is the signature of this class. May be null if the class is not a generic
+ // one, and does not extend or implement generic classes or interfaces.
+
+ if (signature != null) {
+ considerSignature(signature);
+ }
+
+ // superName is the internal of name of the super class (see getInternalName).
+ // For interfaces, the super class is Object. May be null but only for the Object class.
+ considerName(superName);
+
+ // interfaces is the internal names of the class's interfaces (see getInternalName).
+ // May be null.
+ considerNames(interfaces);
+ }
+
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // pass
+ }
+
+ // Visits the end of a class
+ @Override
+ public void visitEnd() {
+ // pass
+ }
+
+ private class MyFieldVisitor extends FieldVisitor {
+
+ public MyFieldVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // pass
+ }
+
+ // Visits the end of a class
+ @Override
+ public void visitEnd() {
+ // pass
+ }
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ // desc is the field's descriptor (see Type).
+ considerDesc(desc);
+
+ // signature is the field's signature. May be null if the field's type does not use
+ // generic types.
+ considerSignature(signature);
+
+ return new MyFieldVisitor();
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ // name is the internal name of an inner class (see getInternalName).
+ // Note: outerName/innerName seems to be null when we're reading the
+ // _Original_ClassName classes generated by layoutlib_create.
+ if (outerName != null) {
+ considerName(name);
+ }
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ // desc is the method's descriptor (see Type).
+ considerDesc(desc);
+ // signature is the method's signature. May be null if the method parameters, return
+ // type and exceptions do not use generic types.
+ considerSignature(signature);
+
+ return new MyMethodVisitor();
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ // pass
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ // pass
+ }
+
+
+ // ---------------------------------------------------
+ // --- MethodVisitor
+ // ---------------------------------------------------
+
+ private class MyMethodVisitor extends MethodVisitor {
+
+ public MyMethodVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitCode() {
+ // pass
+ }
+
+ // field instruction
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ // name is the field's name.
+ // desc is the field's descriptor (see Type).
+ considerDesc(desc);
+ }
+
+ @Override
+ public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) {
+ // pass
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ // pass -- an IINC instruction
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ // pass -- a zero operand instruction
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ // pass -- a single int operand instruction
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ // pass -- a jump instruction
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ // pass -- a label target
+ }
+
+ // instruction to load a constant from the stack
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (cst instanceof Type) {
+ considerType((Type) cst);
+ }
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ // pass
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc,
+ String signature, Label start, Label end, int index) {
+ // desc is the type descriptor of this local variable.
+ considerDesc(desc);
+ // signature is the type signature of this local variable. May be null if the local
+ // variable type does not use generic types.
+ considerSignature(signature);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ // pass -- a lookup switch instruction
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ // pass
+ }
+
+ // instruction that invokes a method
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+
+ // owner is the internal name of the method's owner class
+ if (!considerDesc(owner) && owner.indexOf('/') != -1) {
+ considerName(owner);
+ }
+ // desc is the method's descriptor (see Type).
+ considerDesc(desc);
+ }
+
+ // instruction multianewarray, whatever that is
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+
+ // desc an array type descriptor.
+ considerDesc(desc);
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ // pass -- table switch instruction
+
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ // type is the internal name of the type of exceptions handled by the handler,
+ // or null to catch any exceptions (for "finally" blocks).
+ considerName(type);
+ }
+
+ // type instruction
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ // type is the operand of the instruction to be visited. This operand must be the
+ // internal name of an object or array class.
+ considerName(type);
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ // pass -- local variable instruction
+ }
+ }
+
+ private class MySignatureVisitor extends SignatureVisitor {
+
+ public MySignatureVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ // ---------------------------------------------------
+ // --- SignatureVisitor
+ // ---------------------------------------------------
+
+ private String mCurrentSignatureClass = null;
+
+ // Starts the visit of a signature corresponding to a class or interface type
+ @Override
+ public void visitClassType(String name) {
+ mCurrentSignatureClass = name;
+ considerName(name);
+ }
+
+ // Visits an inner class
+ @Override
+ public void visitInnerClassType(String name) {
+ if (mCurrentSignatureClass != null) {
+ mCurrentSignatureClass += "$" + name;
+ considerName(mCurrentSignatureClass);
+ }
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ // pass -- a primitive type, ignored
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ // pass
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ // pass
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ // pass
+ }
+ }
+
+
+ // ---------------------------------------------------
+ // --- AnnotationVisitor
+ // ---------------------------------------------------
+
+ private class MyAnnotationVisitor extends AnnotationVisitor {
+
+ public MyAnnotationVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ // Visits a primitive value of an annotation
+ @Override
+ public void visit(String name, Object value) {
+ // value is the actual value, whose type must be Byte, Boolean, Character, Short,
+ // Integer, Long, Float, Double, String or Type
+ if (value instanceof Type) {
+ considerType((Type) value);
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ // desc is the class descriptor of the nested annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitEnum(String name, String desc, String value) {
+ // desc is the class descriptor of the enumeration class.
+ considerDesc(desc);
+ }
+ }
+ }
+}
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/Log.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java
new file mode 100644
index 0000000..c3ba591
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java
@@ -0,0 +1,72 @@
+/*
+ * 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 java.io.PrintWriter;
+import java.io.StringWriter;
+
+public class Log {
+
+ private boolean mVerbose = false;
+
+ public void setVerbose(boolean verbose) {
+ mVerbose = verbose;
+ }
+
+ public void debug(String format, Object... args) {
+ if (mVerbose) {
+ info(format, args);
+ }
+ }
+
+ /** Similar to debug() but doesn't do a \n automatically. */
+ public void debugNoln(String format, Object... args) {
+ if (mVerbose) {
+ String s = String.format(format, args);
+ System.out.print(s);
+ }
+ }
+
+ public void info(String format, Object... args) {
+ String s = String.format(format, args);
+ outPrintln(s);
+ }
+
+ public void error(String format, Object... args) {
+ String s = String.format(format, args);
+ errPrintln(s);
+ }
+
+ public void exception(Throwable t, String format, Object... args) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ pw.flush();
+ error(format + "\n" + sw.toString(), args);
+ }
+
+ /** for unit testing */
+ protected void errPrintln(String msg) {
+ System.err.println(msg);
+ }
+
+ /** for unit testing */
+ protected void outPrintln(String msg) {
+ System.out.println(msg);
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java
new file mode 100644
index 0000000..dc4b4a7
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+public class LogAbortException extends Exception {
+
+ private final String mFormat;
+ private final Object[] mArgs;
+
+ public LogAbortException(String format, Object... args) {
+ mFormat = format;
+ mArgs = args;
+ }
+
+ public void error(Log log) {
+ log.error(mFormat, mArgs);
+ }
+}
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
new file mode 100644
index 0000000..9cd74db
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -0,0 +1,212 @@
+/*
+ * 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 java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Entry point for the layoutlib_create tool.
+ * <p/>
+ * The tool does not currently rely on any external configuration file.
+ * Instead the configuration is mostly done via the {@link CreateInfo} class.
+ * <p/>
+ * For a complete description of the tool and its implementation, please refer to
+ * the "README.txt" file at the root of this project.
+ * <p/>
+ * For a quick test, invoke this as follows:
+ * <pre>
+ * $ make layoutlib
+ * </pre>
+ * which does:
+ * <pre>
+ * $ make layoutlib_create &lt;bunch of framework jars&gt;
+ * $ java -jar out/host/linux-x86/framework/layoutlib_create.jar \
+ * 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 class Options {
+ public boolean generatePublicAccess = true;
+ public boolean listAllDeps = false;
+ public boolean listOnlyMissingDeps = false;
+ }
+
+ public static final Options sOptions = new Options();
+
+ public static void main(String[] args) {
+
+ Log log = new Log();
+
+ ArrayList<String> osJarPath = new ArrayList<String>();
+ String[] osDestJar = { null };
+
+ if (!processArgs(log, args, osJarPath, osDestJar)) {
+ log.error("Usage: layoutlib_create [-v] [-p] output.jar input.jar ...");
+ log.error("Usage: layoutlib_create [-v] [--list-deps|--missing-deps] input.jar ...");
+ System.exit(1);
+ }
+
+ if (sOptions.listAllDeps || sOptions.listOnlyMissingDeps) {
+ System.exit(listDeps(osJarPath, log));
+
+ } else {
+ System.exit(createLayoutLib(osDestJar[0], osJarPath, log));
+ }
+
+
+ System.exit(1);
+ }
+
+ private static int createLayoutLib(String osDestJar, ArrayList<String> osJarPath, Log log) {
+ log.info("Output: %1$s", osDestJar);
+ for (String path : osJarPath) {
+ log.info("Input : %1$s", path);
+ }
+
+ try {
+ AsmGenerator agen = new AsmGenerator(log, osDestJar, new CreateInfo());
+
+ AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
+ new String[] { // derived from
+ "android.view.View",
+ "android.app.Fragment"
+ },
+ new String[] { // include classes
+ "android.*", // for android.R
+ "android.util.*",
+ "com.android.internal.util.*",
+ "android.view.*",
+ "android.widget.*",
+ "com.android.internal.widget.*",
+ "android.text.**",
+ "android.graphics.*",
+ "android.graphics.drawable.*",
+ "android.content.*",
+ "android.content.res.*",
+ "org.apache.harmony.xml.*",
+ "com.android.internal.R**",
+ "android.pim.*", // for datepicker
+ "android.os.*", // for android.os.Handler
+ "android.database.ContentObserver", // for Digital clock
+ });
+ aa.analyze();
+ agen.generate();
+
+ // Throw an error if any class failed to get renamed by the generator
+ //
+ // IMPORTANT: if you're building the platform and you get this error message,
+ // it means the renameClasses[] array in AsmGenerator needs to be updated: some
+ // class should have been renamed but it was not found in the input JAR files.
+ Set<String> notRenamed = agen.getClassesNotRenamed();
+ if (notRenamed.size() > 0) {
+ // (80-column guide below for error formatting)
+ // 01234567890123456789012345678901234567890123456789012345678901234567890123456789
+ log.error(
+ "ERROR when running layoutlib_create: the following classes are referenced\n" +
+ "by tools/layoutlib/create but were not actually found in the input JAR files.\n" +
+ "This may be due to some platform classes having been renamed.");
+ for (String fqcn : notRenamed) {
+ log.error("- Class not found: %s", fqcn.replace('/', '.'));
+ }
+ for (String path : osJarPath) {
+ log.info("- Input JAR : %1$s", path);
+ }
+ return 1;
+ }
+
+ return 0;
+ } catch (IOException e) {
+ log.exception(e, "Failed to load jar");
+ } catch (LogAbortException e) {
+ e.error(log);
+ }
+
+ return 1;
+ }
+
+ private static int listDeps(ArrayList<String> osJarPath, Log log) {
+ DependencyFinder df = new DependencyFinder(log);
+ try {
+ List<Map<String, Set<String>>> result = df.findDeps(osJarPath);
+ if (sOptions.listAllDeps) {
+ df.printAllDeps(result);
+ } else if (sOptions.listOnlyMissingDeps) {
+ df.printMissingDeps(result);
+ }
+ } catch (IOException e) {
+ log.exception(e, "Failed to load jar");
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns true if args where properly parsed.
+ * Returns false if program should exit with command-line usage.
+ * <p/>
+ * Note: the String[0] is an output parameter wrapped in an array, since there is no
+ * "out" parameter support.
+ */
+ private static boolean processArgs(Log log, String[] args,
+ ArrayList<String> osJarPath, String[] osDestJar) {
+ boolean needs_dest = true;
+ for (int i = 0; i < args.length; i++) {
+ String s = args[i];
+ if (s.equals("-v")) {
+ log.setVerbose(true);
+ } else if (s.equals("-p")) {
+ sOptions.generatePublicAccess = false;
+ } else if (s.equals("--list-deps")) {
+ sOptions.listAllDeps = true;
+ needs_dest = false;
+ } else if (s.equals("--missing-deps")) {
+ sOptions.listOnlyMissingDeps = true;
+ needs_dest = false;
+ } else if (!s.startsWith("-")) {
+ if (needs_dest && osDestJar[0] == null) {
+ osDestJar[0] = s;
+ } else {
+ osJarPath.add(s);
+ }
+ } else {
+ log.error("Unknow argument: %s", s);
+ return false;
+ }
+ }
+
+ if (osJarPath.isEmpty()) {
+ log.error("Missing parameter: path to input jar");
+ return false;
+ }
+ if (needs_dest && osDestJar[0] == null) {
+ log.error("Missing parameter: path to output jar");
+ return false;
+ }
+
+ sOptions.generatePublicAccess = false;
+
+ return true;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java
new file mode 100644
index 0000000..7d1e4cf
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+
+/**
+ * An adapter to make it easier to use {@link MethodListener}.
+ * <p/>
+ * The adapter calls the void {@link #onInvokeV(String, boolean, Object)} listener
+ * for all types (I, L, F, D and A), returning 0 or null as appropriate.
+ */
+public class MethodAdapter implements MethodListener {
+ /**
+ * A stub method is being invoked.
+ * <p/>
+ * Known limitation: caller arguments are not available.
+ *
+ * @param signature The signature of the method being invoked, composed of the
+ * binary class name followed by the method descriptor (aka argument
+ * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
+ * @param isNative True if the method was a native method.
+ * @param caller The calling object. Null for static methods, "this" for instance methods.
+ */
+ @Override
+ public void onInvokeV(String signature, boolean isNative, Object caller) {
+ }
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return an integer, or a boolean, or a short or a byte.
+ */
+ @Override
+ public int onInvokeI(String signature, boolean isNative, Object caller) {
+ onInvokeV(signature, isNative, caller);
+ return 0;
+ }
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a long.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a long.
+ */
+ @Override
+ public long onInvokeL(String signature, boolean isNative, Object caller) {
+ onInvokeV(signature, isNative, caller);
+ return 0;
+ }
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a float.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a float.
+ */
+ @Override
+ public float onInvokeF(String signature, boolean isNative, Object caller) {
+ onInvokeV(signature, isNative, caller);
+ return 0;
+ }
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a double.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a double.
+ */
+ @Override
+ public double onInvokeD(String signature, boolean isNative, Object caller) {
+ onInvokeV(signature, isNative, caller);
+ return 0;
+ }
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns an object.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return an object.
+ */
+ @Override
+ public Object onInvokeA(String signature, boolean isNative, Object caller) {
+ onInvokeV(signature, isNative, caller);
+ return null;
+ }
+}
+
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
new file mode 100644
index 0000000..6fc2b24
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+
+/**
+ * Interface to allow a method invocation to be listened upon.
+ * <p/>
+ * This is used by {@link OverrideMethod} to register a listener for methods that
+ * have been stubbed by the {@link AsmGenerator}. At runtime the stub will call either a
+ * default global listener or a specific listener based on the method signature.
+ */
+public interface MethodListener {
+ /**
+ * A stub method is being invoked.
+ * <p/>
+ * Known limitation: caller arguments are not available.
+ *
+ * @param signature The signature of the method being invoked, composed of the
+ * binary class name followed by the method descriptor (aka argument
+ * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
+ * @param isNative True if the method was a native method.
+ * @param caller The calling object. Null for static methods, "this" for instance methods.
+ */
+ public void onInvokeV(String signature, boolean isNative, Object caller);
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return an integer, or a boolean, or a short or a byte.
+ */
+ public int onInvokeI(String signature, boolean isNative, Object caller);
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a long.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a long.
+ */
+ public long onInvokeL(String signature, boolean isNative, Object caller);
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a float.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a float.
+ */
+ public float onInvokeF(String signature, boolean isNative, Object caller);
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a double.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a double.
+ */
+ public double onInvokeD(String signature, boolean isNative, Object caller);
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns an object.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return an object.
+ */
+ public Object onInvokeA(String signature, boolean isNative, Object caller);
+}
+
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
new file mode 100644
index 0000000..a6aff99
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
@@ -0,0 +1,151 @@
+/*
+ * 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 java.util.HashMap;
+
+/**
+ * Allows stub methods from LayoutLib to be overriden at runtime.
+ * <p/>
+ * Implementation note: all types required by this class(inner/outer classes & interfaces)
+ * must be referenced by the injectClass argument to {@link AsmGenerator} in Main.java;
+ * Otherwise they won't be accessible in layoutlib.jar at runtime.
+ */
+public final class OverrideMethod {
+
+ /** Map of method overridden. */
+ private static HashMap<String, MethodListener> sMethods = new HashMap<String, MethodListener>();
+ /** Default listener for all method not listed in sMethods. Nothing if null. */
+ private static MethodListener sDefaultListener = null;
+
+ /**
+ * Sets the default listener for all methods not specifically handled.
+ * Null means to do nothing.
+ */
+ public static void setDefaultListener(MethodListener listener) {
+ sDefaultListener = listener;
+ }
+
+ /**
+ * Defines or reset a listener for the given method signature.
+ *
+ * @param signature The signature of the method being invoked, composed of the
+ * binary class name followed by the method descriptor (aka argument
+ * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V"
+ * @param listener The new listener. Removes it if null.
+ */
+ public static void setMethodListener(String signature, MethodListener listener) {
+ if (listener == null) {
+ sMethods.remove(signature);
+ } else {
+ sMethods.put(signature, listener);
+ }
+ }
+
+ /**
+ * Invokes the specific listener for the given signature or the default one if defined.
+ * <p/>
+ * This version invokes the method listener for the void return type.
+ * <p/>
+ * Note: this is not intended to be used by the LayoutLib Bridge. It is intended to be called
+ * by the stubbed methods generated by the LayoutLib_create tool.
+ *
+ * @param signature The signature of the method being invoked, composed of the
+ * binary class name followed by the method descriptor (aka argument
+ * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
+ * @param isNative True if the method was a native method.
+ * @param caller The calling object. Null for static methods, "this" for instance methods.
+ */
+ public static void invokeV(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ i.onInvokeV(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ sDefaultListener.onInvokeV(signature, isNative, caller);
+ }
+ }
+
+ /**
+ * Invokes the specific listener for the int return type.
+ * @see #invokeV(String, boolean, Object)
+ */
+ public static int invokeI(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ return i.onInvokeI(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ return sDefaultListener.onInvokeI(signature, isNative, caller);
+ }
+ return 0;
+ }
+
+ /**
+ * Invokes the specific listener for the long return type.
+ * @see #invokeV(String, boolean, Object)
+ */
+ public static long invokeL(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ return i.onInvokeL(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ return sDefaultListener.onInvokeL(signature, isNative, caller);
+ }
+ return 0;
+ }
+
+ /**
+ * Invokes the specific listener for the float return type.
+ * @see #invokeV(String, boolean, Object)
+ */
+ public static float invokeF(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ return i.onInvokeF(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ return sDefaultListener.onInvokeF(signature, isNative, caller);
+ }
+ return 0;
+ }
+
+ /**
+ * Invokes the specific listener for the double return type.
+ * @see #invokeV(String, boolean, Object)
+ */
+ public static double invokeD(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ return i.onInvokeD(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ return sDefaultListener.onInvokeD(signature, isNative, caller);
+ }
+ return 0;
+ }
+
+ /**
+ * Invokes the specific listener for the object return type.
+ * @see #invokeV(String, boolean, Object)
+ */
+ public static Object invokeA(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ return i.onInvokeA(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ return sDefaultListener.onInvokeA(signature, isNative, caller);
+ }
+ return null;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
new file mode 100644
index 0000000..383cbb8
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
@@ -0,0 +1,463 @@
+/*
+ * 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 org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+import org.objectweb.asm.signature.SignatureWriter;
+
+/**
+ * This class visitor renames a class from a given old name to a given new name.
+ * The class visitor will also rename all inner classes and references in the methods.
+ * <p/>
+ *
+ * For inner classes, this handles only the case where the outer class name changes.
+ * The inner class name should remain the same.
+ */
+public class RenameClassAdapter extends ClassVisitor {
+
+
+ private final String mOldName;
+ private final String mNewName;
+ private String mOldBase;
+ private String mNewBase;
+
+ /**
+ * Creates a class visitor that renames a class from a given old name to a given new name.
+ * The class visitor will also rename all inner classes and references in the methods.
+ * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
+ */
+ public RenameClassAdapter(ClassWriter cv, String oldName, String newName) {
+ super(Opcodes.ASM4, cv);
+ mOldBase = mOldName = oldName;
+ mNewBase = mNewName = newName;
+
+ int pos = mOldName.indexOf('$');
+ if (pos > 0) {
+ mOldBase = mOldName.substring(0, pos);
+ }
+ pos = mNewName.indexOf('$');
+ if (pos > 0) {
+ mNewBase = mNewName.substring(0, pos);
+ }
+
+ assert (mOldBase == null && mNewBase == null) || (mOldBase != null && mNewBase != null);
+ }
+
+
+ /**
+ * Renames a type descriptor, e.g. "Lcom.package.MyClass;"
+ * If the type doesn't need to be renamed, returns the input string as-is.
+ */
+ String renameTypeDesc(String desc) {
+ if (desc == null) {
+ return null;
+ }
+
+ return renameType(Type.getType(desc));
+ }
+
+ /**
+ * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
+ * object element, e.g. "[Lcom.package.MyClass;"
+ * If the type doesn't need to be renamed, returns the internal name of the input type.
+ */
+ String renameType(Type type) {
+ if (type == null) {
+ return null;
+ }
+
+ if (type.getSort() == Type.OBJECT) {
+ String in = type.getInternalName();
+ return "L" + renameInternalType(in) + ";";
+ } else if (type.getSort() == Type.ARRAY) {
+ StringBuilder sb = new StringBuilder();
+ for (int n = type.getDimensions(); n > 0; n--) {
+ sb.append('[');
+ }
+ sb.append(renameType(type.getElementType()));
+ return sb.toString();
+ }
+ return type.getDescriptor();
+ }
+
+ /**
+ * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
+ * object element, e.g. "[Lcom.package.MyClass;".
+ * This is like renameType() except that it returns a Type object.
+ * If the type doesn't need to be renamed, returns the input type object.
+ */
+ Type renameTypeAsType(Type type) {
+ if (type == null) {
+ return null;
+ }
+
+ if (type.getSort() == Type.OBJECT) {
+ String in = type.getInternalName();
+ String newIn = renameInternalType(in);
+ if (newIn != in) {
+ return Type.getType("L" + newIn + ";");
+ }
+ } else if (type.getSort() == Type.ARRAY) {
+ StringBuilder sb = new StringBuilder();
+ for (int n = type.getDimensions(); n > 0; n--) {
+ sb.append('[');
+ }
+ sb.append(renameType(type.getElementType()));
+ return Type.getType(sb.toString());
+ }
+ return type;
+ }
+
+ /**
+ * Renames an internal type name, e.g. "com.package.MyClass".
+ * If the type doesn't need to be renamed, returns the input string as-is.
+ * <p/>
+ * The internal type of some of the MethodVisitor turns out to be a type
+ descriptor sometimes so descriptors are renamed too.
+ */
+ String renameInternalType(String type) {
+ if (type == null) {
+ return null;
+ }
+
+ if (type.equals(mOldName)) {
+ return mNewName;
+ }
+
+ if (mOldBase != mOldName && type.equals(mOldBase)) {
+ return mNewBase;
+ }
+
+ int pos = type.indexOf('$');
+ if (pos == mOldBase.length() && type.startsWith(mOldBase)) {
+ return mNewBase + type.substring(pos);
+ }
+
+ // The internal type of some of the MethodVisitor turns out to be a type
+ // descriptor sometimes. This is the case with visitTypeInsn(type) and
+ // visitMethodInsn(owner). We try to detect it and adjust it here.
+ if (type.indexOf(';') > 0) {
+ type = renameTypeDesc(type);
+ }
+
+ return type;
+ }
+
+ /**
+ * Renames a method descriptor, i.e. applies renameType to all arguments and to the
+ * return value.
+ */
+ String renameMethodDesc(String desc) {
+ if (desc == null) {
+ return null;
+ }
+
+ Type[] args = Type.getArgumentTypes(desc);
+
+ StringBuilder sb = new StringBuilder("(");
+ for (Type arg : args) {
+ String name = renameType(arg);
+ sb.append(name);
+ }
+ sb.append(')');
+
+ Type ret = Type.getReturnType(desc);
+ String name = renameType(ret);
+ sb.append(name);
+
+ return sb.toString();
+ }
+
+
+ /**
+ * Renames the ClassSignature handled by ClassVisitor.visit
+ * or the MethodTypeSignature handled by ClassVisitor.visitMethod.
+ */
+ String renameTypeSignature(String sig) {
+ if (sig == null) {
+ return null;
+ }
+ SignatureReader reader = new SignatureReader(sig);
+ SignatureWriter writer = new SignatureWriter();
+ reader.accept(new RenameSignatureAdapter(writer));
+ sig = writer.toString();
+ return sig;
+ }
+
+
+ /**
+ * Renames the FieldTypeSignature handled by ClassVisitor.visitField
+ * or MethodVisitor.visitLocalVariable.
+ */
+ String renameFieldSignature(String sig) {
+ if (sig == null) {
+ return null;
+ }
+ SignatureReader reader = new SignatureReader(sig);
+ SignatureWriter writer = new SignatureWriter();
+ reader.acceptType(new RenameSignatureAdapter(writer));
+ sig = writer.toString();
+ return sig;
+ }
+
+
+ //----------------------------------
+ // Methods from the ClassAdapter
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ name = renameInternalType(name);
+ superName = renameInternalType(superName);
+ signature = renameTypeSignature(signature);
+
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ assert outerName.equals(mOldName);
+ outerName = renameInternalType(outerName);
+ name = outerName + "$" + innerName;
+ super.visitInnerClass(name, outerName, innerName, access);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ desc = renameMethodDesc(desc);
+ signature = renameTypeSignature(signature);
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+ return new RenameMethodAdapter(mw);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ desc = renameTypeDesc(desc);
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ desc = renameTypeDesc(desc);
+ signature = renameFieldSignature(signature);
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+
+ //----------------------------------
+
+ /**
+ * A method visitor that renames all references from an old class name to a new class name.
+ */
+ public class RenameMethodAdapter extends MethodVisitor {
+
+ /**
+ * Creates a method visitor that renames all references from a given old name to a given new
+ * name. The method visitor will also rename all inner classes.
+ * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
+ */
+ public RenameMethodAdapter(MethodVisitor mv) {
+ super(Opcodes.ASM4, mv);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ desc = renameTypeDesc(desc);
+
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+ desc = renameTypeDesc(desc);
+
+ return super.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ type = renameInternalType(type);
+
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ owner = renameInternalType(owner);
+ desc = renameTypeDesc(desc);
+
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ owner = renameInternalType(owner);
+ desc = renameMethodDesc(desc);
+
+ super.visitMethodInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ // If cst is a Type, this means the code is trying to pull the .class constant
+ // for this class, so it needs to be renamed too.
+ if (cst instanceof Type) {
+ cst = renameTypeAsType((Type) cst);
+ }
+ super.visitLdcInsn(cst);
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ desc = renameTypeDesc(desc);
+
+ super.visitMultiANewArrayInsn(desc, dims);
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ type = renameInternalType(type);
+
+ super.visitTryCatchBlock(start, end, handler, type);
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ desc = renameTypeDesc(desc);
+ signature = renameFieldSignature(signature);
+
+ super.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+
+ }
+
+ //----------------------------------
+
+ public class RenameSignatureAdapter extends SignatureVisitor {
+
+ private final SignatureVisitor mSv;
+
+ public RenameSignatureAdapter(SignatureVisitor sv) {
+ super(Opcodes.ASM4);
+ mSv = sv;
+ }
+
+ @Override
+ public void visitClassType(String name) {
+ name = renameInternalType(name);
+ mSv.visitClassType(name);
+ }
+
+ @Override
+ public void visitInnerClassType(String name) {
+ name = renameInternalType(name);
+ mSv.visitInnerClassType(name);
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ SignatureVisitor sv = mSv.visitArrayType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ mSv.visitBaseType(descriptor);
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ SignatureVisitor sv = mSv.visitClassBound();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitEnd() {
+ mSv.visitEnd();
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ SignatureVisitor sv = mSv.visitExceptionType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ mSv.visitFormalTypeParameter(name);
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ SignatureVisitor sv = mSv.visitInterface();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ SignatureVisitor sv = mSv.visitInterfaceBound();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ SignatureVisitor sv = mSv.visitParameterType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ SignatureVisitor sv = mSv.visitReturnType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ SignatureVisitor sv = mSv.visitSuperclass();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ mSv.visitTypeArgument();
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ SignatureVisitor sv = mSv.visitTypeArgument(wildcard);
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ mSv.visitTypeVariable(name);
+ }
+
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
new file mode 100644
index 0000000..51e7535
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
@@ -0,0 +1,376 @@
+/*
+ * 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 org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * This method adapter rewrites a method by discarding the original code and generating
+ * a stub depending on the return type. Original annotations are passed along unchanged.
+ */
+class StubMethodAdapter extends MethodVisitor {
+
+ private static String CONSTRUCTOR = "<init>";
+ private static String CLASS_INIT = "<clinit>";
+
+ /** The parent method writer */
+ private MethodVisitor mParentVisitor;
+ /** The method return type. Can be null. */
+ private Type mReturnType;
+ /** Message to be printed by stub methods. */
+ private String mInvokeSignature;
+ /** Flag to output the first line number. */
+ private boolean mOutputFirstLineNumber = true;
+ /** Flag that is true when implementing a constructor, to accept all original
+ * code calling the original super constructor. */
+ private boolean mIsInitMethod = false;
+
+ private boolean mMessageGenerated;
+ private final boolean mIsStatic;
+ private final boolean mIsNative;
+
+ public StubMethodAdapter(MethodVisitor mv, String methodName, Type returnType,
+ String invokeSignature, boolean isStatic, boolean isNative) {
+ super(Opcodes.ASM4);
+ mParentVisitor = mv;
+ mReturnType = returnType;
+ mInvokeSignature = invokeSignature;
+ mIsStatic = isStatic;
+ mIsNative = isNative;
+
+ if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
+ mIsInitMethod = true;
+ }
+ }
+
+ private void generateInvoke() {
+ /* Generates the code:
+ * OverrideMethod.invoke("signature", mIsNative ? true : false, null or this);
+ */
+ mParentVisitor.visitLdcInsn(mInvokeSignature);
+ // push true or false
+ mParentVisitor.visitInsn(mIsNative ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
+ // push null or this
+ if (mIsStatic) {
+ mParentVisitor.visitInsn(Opcodes.ACONST_NULL);
+ } else {
+ mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+ }
+
+ int sort = mReturnType != null ? mReturnType.getSort() : Type.VOID;
+ switch(sort) {
+ case Type.VOID:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeV",
+ "(Ljava/lang/String;ZLjava/lang/Object;)V");
+ mParentVisitor.visitInsn(Opcodes.RETURN);
+ break;
+ case Type.BOOLEAN:
+ case Type.CHAR:
+ case Type.BYTE:
+ case Type.SHORT:
+ case Type.INT:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeI",
+ "(Ljava/lang/String;ZLjava/lang/Object;)I");
+ switch(sort) {
+ case Type.BOOLEAN:
+ Label l1 = new Label();
+ mParentVisitor.visitJumpInsn(Opcodes.IFEQ, l1);
+ mParentVisitor.visitInsn(Opcodes.ICONST_1);
+ mParentVisitor.visitInsn(Opcodes.IRETURN);
+ mParentVisitor.visitLabel(l1);
+ mParentVisitor.visitInsn(Opcodes.ICONST_0);
+ break;
+ case Type.CHAR:
+ mParentVisitor.visitInsn(Opcodes.I2C);
+ break;
+ case Type.BYTE:
+ mParentVisitor.visitInsn(Opcodes.I2B);
+ break;
+ case Type.SHORT:
+ mParentVisitor.visitInsn(Opcodes.I2S);
+ break;
+ }
+ mParentVisitor.visitInsn(Opcodes.IRETURN);
+ break;
+ case Type.LONG:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeL",
+ "(Ljava/lang/String;ZLjava/lang/Object;)J");
+ mParentVisitor.visitInsn(Opcodes.LRETURN);
+ break;
+ case Type.FLOAT:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeF",
+ "(Ljava/lang/String;ZLjava/lang/Object;)F");
+ mParentVisitor.visitInsn(Opcodes.FRETURN);
+ break;
+ case Type.DOUBLE:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeD",
+ "(Ljava/lang/String;ZLjava/lang/Object;)D");
+ mParentVisitor.visitInsn(Opcodes.DRETURN);
+ break;
+ case Type.ARRAY:
+ case Type.OBJECT:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeA",
+ "(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;");
+ mParentVisitor.visitTypeInsn(Opcodes.CHECKCAST, mReturnType.getInternalName());
+ mParentVisitor.visitInsn(Opcodes.ARETURN);
+ break;
+ }
+
+ }
+
+ private void generatePop() {
+ /* Pops the stack, depending on the return type.
+ */
+ switch(mReturnType != null ? mReturnType.getSort() : Type.VOID) {
+ case Type.VOID:
+ break;
+ case Type.BOOLEAN:
+ case Type.CHAR:
+ case Type.BYTE:
+ case Type.SHORT:
+ case Type.INT:
+ case Type.FLOAT:
+ case Type.ARRAY:
+ case Type.OBJECT:
+ mParentVisitor.visitInsn(Opcodes.POP);
+ break;
+ case Type.LONG:
+ case Type.DOUBLE:
+ mParentVisitor.visitInsn(Opcodes.POP2);
+ break;
+ }
+ }
+
+ /* Pass down to visitor writer. In this implementation, either do nothing. */
+ @Override
+ public void visitCode() {
+ mParentVisitor.visitCode();
+ }
+
+ /*
+ * visitMaxs is called just before visitEnd if there was any code to rewrite.
+ * For non-constructor, generate the messaging code and the return statement
+ * if it hasn't been done before.
+ */
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ if (!mIsInitMethod && !mMessageGenerated) {
+ generateInvoke();
+ mMessageGenerated = true;
+ }
+ mParentVisitor.visitMaxs(maxStack, maxLocals);
+ }
+
+ /**
+ * End of visiting.
+ * For non-constructor, generate the messaging code and the return statement
+ * if it hasn't been done before.
+ */
+ @Override
+ public void visitEnd() {
+ if (!mIsInitMethod && !mMessageGenerated) {
+ generateInvoke();
+ mMessageGenerated = true;
+ mParentVisitor.visitMaxs(1, 1);
+ }
+ mParentVisitor.visitEnd();
+ }
+
+ /* Writes all annotation from the original method. */
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return mParentVisitor.visitAnnotation(desc, visible);
+ }
+
+ /* Writes all annotation default values from the original method. */
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return mParentVisitor.visitAnnotationDefault();
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ /* Writes all attributes from the original method. */
+ @Override
+ 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.
+ */
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ if (mIsInitMethod || mOutputFirstLineNumber) {
+ mParentVisitor.visitLineNumber(line, start);
+ mOutputFirstLineNumber = false;
+ }
+ }
+
+ /**
+ * For non-constructor, rewrite existing "return" instructions to write the message.
+ */
+ @Override
+ public void visitInsn(int opcode) {
+ if (mIsInitMethod) {
+ switch (opcode) {
+ case Opcodes.RETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.DRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.IRETURN:
+ case Opcodes.LRETURN:
+ // Pop the last word from the stack since invoke will generate its own return.
+ generatePop();
+ generateInvoke();
+ mMessageGenerated = true;
+ //$FALL-THROUGH$
+ default:
+ mParentVisitor.visitInsn(opcode);
+ }
+ }
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitLabel(label);
+ }
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitTryCatchBlock(start, end, handler, type);
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitMethodInsn(opcode, owner, name, desc);
+ }
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ @Override
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitFrame(type, nLocal, local, nStack, stack);
+ }
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitIincInsn(var, increment);
+ }
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitIntInsn(opcode, operand);
+ }
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitJumpInsn(opcode, label);
+ }
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitLdcInsn(cst);
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitMultiANewArrayInsn(desc, dims);
+ }
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitTypeInsn(opcode, type);
+ }
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitVarInsn(opcode, var);
+ }
+ }
+
+}
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
new file mode 100644
index 0000000..d45a183
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -0,0 +1,183 @@
+/*
+ * 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 org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Set;
+
+/**
+ * Class adapter that can stub some or all of the methods of the class.
+ */
+class TransformClassAdapter extends ClassVisitor {
+
+ /** True if all methods should be stubbed, false if only native ones must be stubbed. */
+ private final boolean mStubAll;
+ /** True if the class is an interface. */
+ private boolean mIsInterface;
+ private final String mClassName;
+ private final Log mLog;
+ private final Set<String> mStubMethods;
+ private Set<String> mDeleteReturns;
+
+ /**
+ * Creates a new class adapter that will stub some or all methods.
+ * @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
+ * methods should be stubbed.
+ * @param hasNative True if the method has natives, in which case its access should be
+ * changed.
+ */
+ public TransformClassAdapter(Log logger, Set<String> stubMethods,
+ Set<String> deleteReturns, String className, ClassVisitor cv,
+ boolean stubNativesOnly, boolean hasNative) {
+ super(Opcodes.ASM4, cv);
+ mLog = logger;
+ mStubMethods = stubMethods;
+ mClassName = className;
+ mStubAll = !stubNativesOnly;
+ mIsInterface = false;
+ mDeleteReturns = deleteReturns;
+ }
+
+ /* Visits the class header. */
+ @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
+ if (Main.sOptions.generatePublicAccess) {
+ access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
+ access |= Opcodes.ACC_PUBLIC;
+ }
+ // remove final
+ access = access & ~Opcodes.ACC_FINAL;
+ // note: leave abstract classes as such
+ // don't try to implement stub for interfaces
+
+ 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) {
+ // remove protected or private and set as public
+ if (Main.sOptions.generatePublicAccess) {
+ access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
+ access |= Opcodes.ACC_PUBLIC;
+ }
+ // remove final
+ access = access & ~Opcodes.ACC_FINAL;
+ // note: leave abstract classes as such
+ // don't try to implement stub for interfaces
+
+ super.visitInnerClass(name, outerName, innerName, access);
+ }
+
+ /* Visits a method. */
+ @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) {
+ String returnType = t.getInternalName();
+ if (returnType != null) {
+ if (mDeleteReturns.contains(returnType)) {
+ return null;
+ }
+ }
+ }
+ }
+
+ String methodSignature = mClassName.replace('/', '.') + "#" + name;
+
+ // change access to public
+ if (Main.sOptions.generatePublicAccess) {
+ access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
+ access |= Opcodes.ACC_PUBLIC;
+ }
+
+ // remove final
+ access = access & ~Opcodes.ACC_FINAL;
+
+ // stub this method if they are all to be stubbed or if it is a native method
+ // and don't try to stub interfaces nor abstract non-native methods.
+ if (!mIsInterface &&
+ ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != Opcodes.ACC_ABSTRACT) &&
+ (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);
+
+ } else {
+ mLog.debug(" Keep: %s %s", name, desc);
+ 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,
+ Object value) {
+ // change access to public
+ if (Main.sOptions.generatePublicAccess) {
+ access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
+ access |= Opcodes.ACC_PUBLIC;
+ }
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ /**
+ * Extracts the return {@link Type} of this descriptor.
+ */
+ Type returnType(String desc) {
+ if (desc != null) {
+ try {
+ return Type.getReturnType(desc);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // ignore, not a valid type.
+ }
+ }
+ return null;
+ }
+}
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
new file mode 100644
index 0000000..d6dba6a
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Unit tests for some methods of {@link AsmAnalyzer}.
+ */
+public class AsmAnalyzerTest {
+
+ private MockLog mLog;
+ private ArrayList<String> mOsJarPath;
+ private AsmAnalyzer mAa;
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
+
+ mOsJarPath = new ArrayList<String>();
+ mOsJarPath.add(url.getFile());
+
+ mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */,
+ null /* deriveFrom */, null /* includeGlobs */ );
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testParseZip() throws IOException {
+ Map<String, ClassReader> map = mAa.parseZip(mOsJarPath);
+
+ assertArrayEquals(new String[] {
+ "mock_android.dummy.InnerTest",
+ "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.view.View",
+ "mock_android.view.ViewGroup",
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams",
+ "mock_android.widget.LinearLayout",
+ "mock_android.widget.LinearLayout$LayoutParams",
+ "mock_android.widget.TableLayout",
+ "mock_android.widget.TableLayout$LayoutParams"
+ },
+ map.keySet().toArray());
+ }
+
+ @Test
+ public void testFindClass() throws IOException, LogAbortException {
+ Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+ TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+
+ 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" },
+ found.keySet().toArray());
+ assertArrayEquals(new ClassReader[] { cr }, found.values().toArray());
+ }
+
+ @Test
+ public void testFindGlobs() throws IOException, LogAbortException {
+ Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+ TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+
+ // this matches classes, a package match returns nothing
+ found.clear();
+ mAa.findGlobs("mock_android.view", zipClasses, found);
+
+ assertArrayEquals(new String[] { },
+ found.keySet().toArray());
+
+ // a complex glob search. * is a search pattern that matches names, not dots
+ mAa.findGlobs("mock_android.*.*Group$*Layout*", zipClasses, found);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams"
+ },
+ found.keySet().toArray());
+
+ // a complex glob search. ** is a search pattern that matches names including dots
+ mAa.findGlobs("mock_android.**Group*", zipClasses, found);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.ViewGroup",
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams"
+ },
+ found.keySet().toArray());
+
+ // matches a single class
+ found.clear();
+ mAa.findGlobs("mock_android.view.View", zipClasses, found);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.View"
+ },
+ found.keySet().toArray());
+
+ // matches everyting inside the given package but not sub-packages
+ found.clear();
+ mAa.findGlobs("mock_android.view.*", zipClasses, found);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.View",
+ "mock_android.view.ViewGroup",
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams"
+ },
+ 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 testFindClassesDerivingFrom() throws LogAbortException, IOException {
+ Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+ TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+
+ mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.View",
+ "mock_android.view.ViewGroup",
+ "mock_android.widget.LinearLayout",
+ "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);
+ TreeMap<String, ClassReader> keep = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> out_deps = new TreeMap<String, ClassReader>();
+
+ 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 */);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.ViewGroup",
+ "mock_android.widget.TableLayout$LayoutParams",
+ },
+ out_deps.keySet().toArray());
+
+ in_deps.putAll(out_deps);
+ out_deps.clear();
+
+ // get second level dependencies
+ for (ClassReader cr2 : in_deps.values()) {
+ cr2.accept(visitor, 0 /* flags */);
+ }
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.View",
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams",
+ },
+ out_deps.keySet().toArray());
+
+ in_deps.putAll(out_deps);
+ out_deps.clear();
+
+ // get third level dependencies (there are none)
+ for (ClassReader cr2 : in_deps.values()) {
+ cr2.accept(visitor, 0 /* flags */);
+ }
+
+ assertArrayEquals(new String[] { }, out_deps.keySet().toArray());
+ }
+}
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
new file mode 100644
index 0000000..7b76a5b
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 static org.junit.Assert.assertArrayEquals;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * Unit tests for some methods of {@link AsmGenerator}.
+ */
+public class AsmGeneratorTest {
+
+ private MockLog mLog;
+ private ArrayList<String> mOsJarPath;
+ private String mOsDestJar;
+ private File mTempFile;
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
+
+ mOsJarPath = new ArrayList<String>();
+ mOsJarPath.add(url.getFile());
+
+ mTempFile = File.createTempFile("mock", "jar");
+ mOsDestJar = mTempFile.getAbsolutePath();
+ mTempFile.deleteOnExit();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mTempFile != null) {
+ mTempFile.delete();
+ mTempFile = null;
+ }
+ }
+
+ @Test
+ public void testClassRenaming() throws IOException, LogAbortException {
+
+ ICreateInfo ci = new ICreateInfo() {
+ @Override
+ public Class<?>[] getInjectedClasses() {
+ // classes to inject in the final JAR
+ return new Class<?>[0];
+ }
+
+ @Override
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ @Override
+ 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",
+ };
+ }
+
+ @Override
+ 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
+ new String[] { // include classes
+ "**"
+ });
+ 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..6e120ce
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -0,0 +1,463 @@
+/*
+ * 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 DelegateMethodAdapter2} 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");
+ delegateMethods.add("privateMethod");
+
+ // 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));
+ assertEquals(1+10+20, callGet_Original(o2, 10, 20));
+
+ // The original Outer has a private method that is
+ // delegated. We should be able to call both the delegate
+ // and the original (which is now public).
+ assertEquals("outerPrivateMethod",
+ callMethod(o2, "privateMethod_Original", false /*makePublic*/));
+
+ // The original method is private, so by default we can't access it
+ boolean gotIllegalAccessException = false;
+ try {
+ callMethod(o2, "privateMethod", false /*makePublic*/);
+ } catch(IllegalAccessException e) {
+ gotIllegalAccessException = true;
+ }
+ assertTrue(gotIllegalAccessException);
+ // Try again, but now making it accessible
+ assertEquals("outerPrivate_Delegate",
+ callMethod(o2, "privateMethod", true /*makePublic*/));
+
+ // 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));
+ assertEquals(3+10+20, callGet_Original(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 the "_Original" methods for {@link OuterClass#get}
+ * or {@link InnerClass#get}via reflection.
+ */
+ public int callGet_Original(Object instance, int a, long b) throws Exception {
+ Method m = instance.getClass().getMethod("get_Original",
+ new Class<?>[] { int.class, long.class } );
+
+ Object result = m.invoke(instance, new Object[] { a, b });
+ return ((Integer) result).intValue();
+ }
+
+ /**
+ * Accesses the any declared method that takes no parameter via reflection.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T callMethod(Object instance, String methodName, boolean makePublic) throws Exception {
+ Method m = instance.getClass().getDeclaredMethod(methodName, (Class<?>[])null);
+
+ boolean wasAccessible = m.isAccessible();
+ if (makePublic && !wasAccessible) {
+ m.setAccessible(true);
+ }
+
+ Object result = m.invoke(instance, (Object[])null);
+
+ if (makePublic && !wasAccessible) {
+ m.setAccessible(false);
+ }
+
+ return (T) result;
+ }
+
+ /**
+ * 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
new file mode 100644
index 0000000..1a5f653
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LogTest {
+
+ private MockLog mLog;
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // pass
+ }
+
+ @Test
+ public void testDebug() {
+ assertEquals("", mLog.getOut());
+ assertEquals("", mLog.getErr());
+
+ mLog.setVerbose(false);
+ mLog.debug("Test %d", 42);
+ assertEquals("", mLog.getOut());
+
+ mLog.setVerbose(true);
+ mLog.debug("Test %d", 42);
+
+ assertEquals("Test 42\n", mLog.getOut());
+ assertEquals("", mLog.getErr());
+ }
+
+ @Test
+ public void testInfo() {
+ assertEquals("", mLog.getOut());
+ assertEquals("", mLog.getErr());
+
+ mLog.info("Test %d", 43);
+
+ assertEquals("Test 43\n", mLog.getOut());
+ assertEquals("", mLog.getErr());
+ }
+
+ @Test
+ public void testError() {
+ assertEquals("", mLog.getOut());
+ assertEquals("", mLog.getErr());
+
+ mLog.error("Test %d", 44);
+
+ assertEquals("", mLog.getOut());
+ assertEquals("Test 44\n", mLog.getErr());
+ }
+
+ @Test
+ public void testException() {
+ assertEquals("", mLog.getOut());
+ assertEquals("", mLog.getErr());
+
+ Exception e = new Exception("My Exception");
+ mLog.exception(e, "Test %d", 44);
+
+ assertEquals("", mLog.getOut());
+ assertTrue(mLog.getErr().startsWith("Test 44\njava.lang.Exception: My Exception"));
+ }
+}
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/RenameClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
new file mode 100644
index 0000000..90c6a9c
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class RenameClassAdapterTest {
+
+ private RenameClassAdapter mOuter;
+ private RenameClassAdapter mInner;
+
+ @Before
+ public void setUp() throws Exception {
+ mOuter = new RenameClassAdapter(null, // cv
+ "com.pack.Old",
+ "org.blah.New");
+
+ mInner = new RenameClassAdapter(null, // cv
+ "com.pack.Old$Inner",
+ "org.blah.New$Inner");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ /**
+ * Renames a type, e.g. "Lcom.package.My;"
+ * If the type doesn't need to be renamed, returns the input string as-is.
+ */
+ @Test
+ public void testRenameTypeDesc() {
+
+ // primitive types are left untouched
+ assertEquals("I", mOuter.renameTypeDesc("I"));
+ assertEquals("D", mOuter.renameTypeDesc("D"));
+ assertEquals("V", mOuter.renameTypeDesc("V"));
+
+ // object types that need no renaming are left untouched
+ assertEquals("Lcom.package.MyClass;", mOuter.renameTypeDesc("Lcom.package.MyClass;"));
+ assertEquals("Lcom.package.MyClass;", mInner.renameTypeDesc("Lcom.package.MyClass;"));
+
+ // object types that match the requirements
+ assertEquals("Lorg.blah.New;", mOuter.renameTypeDesc("Lcom.pack.Old;"));
+ assertEquals("Lorg.blah.New$Inner;", mInner.renameTypeDesc("Lcom.pack.Old$Inner;"));
+ // inner classes match the base type which is being renamed
+ assertEquals("Lorg.blah.New$Other;", mOuter.renameTypeDesc("Lcom.pack.Old$Other;"));
+ assertEquals("Lorg.blah.New$Other;", mInner.renameTypeDesc("Lcom.pack.Old$Other;"));
+
+ // arrays
+ assertEquals("[Lorg.blah.New;", mOuter.renameTypeDesc("[Lcom.pack.Old;"));
+ assertEquals("[[Lorg.blah.New;", mOuter.renameTypeDesc("[[Lcom.pack.Old;"));
+
+ assertEquals("[Lorg.blah.New;", mInner.renameTypeDesc("[Lcom.pack.Old;"));
+ assertEquals("[[Lorg.blah.New;", mInner.renameTypeDesc("[[Lcom.pack.Old;"));
+ }
+
+ /**
+ * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
+ * object element, e.g. "[Lcom.package.MyClass;"
+ * If the type doesn't need to be renamed, returns the internal name of the input type.
+ */
+ @Test
+ public void testRenameType() {
+ // Skip. This is actually tested by testRenameTypeDesc above.
+ }
+
+ /**
+ * Renames an internal type name, e.g. "com.package.MyClass".
+ * If the type doesn't need to be renamed, returns the input string as-is.
+ */
+ @Test
+ public void testRenameInternalType() {
+ // a descriptor is not left untouched
+ assertEquals("Lorg.blah.New;", mOuter.renameInternalType("Lcom.pack.Old;"));
+ assertEquals("Lorg.blah.New$Inner;", mOuter.renameInternalType("Lcom.pack.Old$Inner;"));
+
+ // an actual FQCN
+ assertEquals("org.blah.New", mOuter.renameInternalType("com.pack.Old"));
+ assertEquals("org.blah.New$Inner", mOuter.renameInternalType("com.pack.Old$Inner"));
+
+ assertEquals("org.blah.New$Other", mInner.renameInternalType("com.pack.Old$Other"));
+ assertEquals("org.blah.New$Other", mInner.renameInternalType("com.pack.Old$Other"));
+ }
+
+ /**
+ * Renames a method descriptor, i.e. applies renameType to all arguments and to the
+ * return value.
+ */
+ @Test
+ public void testRenameMethodDesc() {
+ assertEquals("(IDLorg.blah.New;[Lorg.blah.New$Inner;)Lorg.blah.New$Other;",
+ mOuter.renameMethodDesc("(IDLcom.pack.Old;[Lcom.pack.Old$Inner;)Lcom.pack.Old$Other;"));
+ }
+
+
+
+}
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..f083e76
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.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.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 2 + 1 + a + b
+ public int get(int a, long b) {
+ return 2 + mOuterValue + a + (int) b;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private String privateMethod() {
+ return "outerPrivateMethod";
+ }
+}
+
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..774be8e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+ }
+
+ public static String privateMethod(OuterClass instance) {
+ return "outerPrivate_Delegate";
+ }
+}
+
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;
+ }
+}
diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar
new file mode 100644
index 0000000..a7ea74f
--- /dev/null
+++ b/tools/layoutlib/create/tests/data/mock_android.jar
Binary files differ
diff --git a/tools/layoutlib/create/tests/data/mock_android.jardesc b/tools/layoutlib/create/tests/data/mock_android.jardesc
new file mode 100644
index 0000000..95f7591
--- /dev/null
+++ b/tools/layoutlib/create/tests/data/mock_android.jardesc
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="WINDOWS-1252" standalone="no"?>
+<jardesc>
+ <jar path="C:/ralf/google/src/raphael-lapdroid/device/tools/layoutlib/create/tests/data/mock_android.jar"/>
+ <options buildIfNeeded="true" compress="true" descriptionLocation="/layoutlib_create/tests/data/mock_android.jardesc" exportErrors="true" exportWarnings="true" includeDirectoryEntries="false" overwrite="false" saveDescription="true" storeRefactorings="false" useSourceFolders="false"/>
+ <storedRefactorings deprecationInfo="true" structuralOnly="false"/>
+ <selectedProjects/>
+ <manifest generateManifest="true" manifestLocation="" manifestVersion="1.0" reuseManifest="false" saveManifest="false" usesManifest="true">
+ <sealing sealJar="false">
+ <packagesToSeal/>
+ <packagesToUnSeal/>
+ </sealing>
+ </manifest>
+ <selectedElements exportClassFiles="true" exportJavaFiles="false" exportOutputFolder="false">
+ <javaElement handleIdentifier="=layoutlib_create/tests&lt;mock_android.widget"/>
+ <javaElement handleIdentifier="=layoutlib_create/tests&lt;mock_android.view"/>
+ <javaElement handleIdentifier="=layoutlib_create/tests&lt;mock_android.dummy"/>
+ </selectedElements>
+</jardesc>
diff --git a/tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java b/tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java
new file mode 100644
index 0000000..e355ead
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 mock_android.dummy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+public class InnerTest {
+
+ private int mSomeField;
+ private MyStaticInnerClass mInnerInstance;
+ private MyIntEnum mTheIntEnum;
+ private MyGenerics1<int[][], InnerTest, MyIntEnum, float[]> mGeneric1;
+
+ public class NotStaticInner2 extends NotStaticInner1 {
+
+ }
+
+ public class NotStaticInner1 {
+
+ public void someThing() {
+ mSomeField = 2;
+ mInnerInstance = null;
+ }
+
+ }
+
+ private static class MyStaticInnerClass {
+
+ }
+
+ private static class DerivingClass extends InnerTest {
+
+ }
+
+ // enums are a kind of inner static class
+ public enum MyIntEnum {
+ VALUE0(0),
+ VALUE1(1),
+ VALUE2(2);
+
+ MyIntEnum(int myInt) {
+ this.myInt = myInt;
+ }
+ final int myInt;
+ }
+
+ public static class MyGenerics1<T, U, V, W> {
+ public MyGenerics1() {
+ int a = 1;
+ }
+ }
+
+ public <X> void genericMethod1(X a, X[] a) {
+ }
+
+ public <X, Y> void genericMethod2(X a, List<Y> b) {
+ }
+
+ public <X, Y> void genericMethod3(X a, List<Y extends InnerTest> b) {
+ }
+
+ public <T extends InnerTest> void genericMethod4(T[] a, Collection<T> b, Collection<?> c) {
+ Iterator<T> i = b.iterator();
+ }
+
+ public void someMethod(InnerTest self) {
+ mSomeField = self.mSomeField;
+ MyStaticInnerClass m = new MyStaticInnerClass();
+ mInnerInstance = m;
+ mTheIntEnum = null;
+ mGeneric1 = new MyGenerics1();
+ genericMethod(new DerivingClass[0], new ArrayList<DerivingClass>(), new ArrayList<InnerTest>());
+ }
+}
diff --git a/tools/layoutlib/create/tests/mock_android/view/View.java b/tools/layoutlib/create/tests/mock_android/view/View.java
new file mode 100644
index 0000000..a80a98d
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_android/view/View.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 mock_android.view;
+
+public class View {
+
+}
diff --git a/tools/layoutlib/create/tests/mock_android/view/ViewGroup.java b/tools/layoutlib/create/tests/mock_android/view/ViewGroup.java
new file mode 100644
index 0000000..466470f
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_android/view/ViewGroup.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 mock_android.view;
+
+public class ViewGroup extends View {
+
+ public class MarginLayoutParams extends LayoutParams {
+
+ }
+
+ public class LayoutParams {
+
+ }
+
+}
diff --git a/tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java b/tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java
new file mode 100644
index 0000000..3870a63
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 mock_android.widget;
+
+import mock_android.view.ViewGroup;
+
+public class LinearLayout extends ViewGroup {
+
+ public class LayoutParams extends mock_android.view.ViewGroup.LayoutParams {
+
+ }
+
+}
diff --git a/tools/layoutlib/create/tests/mock_android/widget/TableLayout.java b/tools/layoutlib/create/tests/mock_android/widget/TableLayout.java
new file mode 100644
index 0000000..e455e7d
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_android/widget/TableLayout.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 mock_android.widget;
+
+import mock_android.view.ViewGroup;
+
+public class TableLayout extends ViewGroup {
+
+ public class LayoutParams extends MarginLayoutParams {
+
+ }
+
+}