diff options
author | Adam Lesinski <adamlesinski@google.com> | 2014-01-23 18:17:42 -0800 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2014-01-27 10:31:04 -0800 |
commit | 282e181b58cf72b6ca770dc7ca5f91f135444502 (patch) | |
tree | e313e7ab30ff4679562efa37bde29cfcb9e375d3 /tools/layoutlib | |
parent | 7023df08f14ec5dee76ac54c03e870f84e297636 (diff) | |
download | frameworks_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')
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 Binary files differnew file mode 100644 index 0000000..84e6bc8 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 0000000..38e4f45 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..bf9f300 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..bd44b52 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png Binary files differnew file mode 100644 index 0000000..a4be298 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..a00bc5b --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 0000000..dc3183b --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..b07f611 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..c629387 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png Binary files differnew file mode 100644 index 0000000..eb7c1a4 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png diff --git a/tools/layoutlib/bridge/resources/bars/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 Binary files differnew file mode 100644 index 0000000..bd60cd6 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 0000000..c5bc5c9 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..f621d9c --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png 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 < 0 or > > 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><className>_Delegate</code> with static methods matching + * the methods to be overridden here. The methods have the same return type. + * The argument type list is the same except the "this" reference is passed first + * for non-static methods. + * <p/> + * A new annotation is added 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 <bunch of framework jars> + * $ 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 Binary files differnew file mode 100644 index 0000000..a7ea74f --- /dev/null +++ b/tools/layoutlib/create/tests/data/mock_android.jar 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<mock_android.widget"/> + <javaElement handleIdentifier="=layoutlib_create/tests<mock_android.view"/> + <javaElement handleIdentifier="=layoutlib_create/tests<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 { + + } + +} |