From 19a021038f2f4683dddef651543d7298f5bd7218 Mon Sep 17 00:00:00 2001 From: Xavier Ducrohet Date: Wed, 15 Dec 2010 19:20:08 -0800 Subject: LayoutLib: Update layoutlib with revised API. Change-Id: I78929df621f48e85d9cbefe1f5590f9ce99bbaff --- .../bridge/src/android/app/Fragment_Delegate.java | 2 +- .../bridge/src/android/graphics/BitmapFactory.java | 2 +- .../src/android/graphics/Bitmap_Delegate.java | 2 +- .../src/com/android/layoutlib/bridge/Bridge.java | 39 +- .../layoutlib/bridge/BridgeLayoutScene.java | 174 --- .../layoutlib/bridge/BridgeRenderSession.java | 175 +++ .../layoutlib/bridge/android/BridgeContext.java | 12 +- .../layoutlib/bridge/android/BridgeInflater.java | 8 +- .../layoutlib/bridge/android/BridgeResources.java | 4 +- .../layoutlib/bridge/android/BridgeTypedArray.java | 6 +- .../bridge/android/BridgeXmlBlockParser.java | 15 +- .../bridge/android/BridgeXmlPullAttributes.java | 2 +- .../layoutlib/bridge/impl/AnimationThread.java | 31 +- .../layoutlib/bridge/impl/LayoutSceneImpl.java | 1157 ------------------- .../layoutlib/bridge/impl/PlayAnimationThread.java | 12 +- .../layoutlib/bridge/impl/RenderSessionImpl.java | 1161 ++++++++++++++++++++ .../layoutlib/bridge/impl/ResourceHelper.java | 6 +- 17 files changed, 1409 insertions(+), 1399 deletions(-) delete mode 100644 tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java create mode 100644 tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java delete mode 100644 tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java create mode 100644 tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java (limited to 'tools/layoutlib') diff --git a/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java index b46d95c..60ad645 100644 --- a/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java +++ b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java @@ -16,7 +16,7 @@ package android.app; -import com.android.layoutlib.api.IProjectCallback; +import com.android.ide.common.rendering.api.IProjectCallback; import android.content.Context; import android.os.Bundle; diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java index 626f878..c57aef6 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java @@ -16,7 +16,7 @@ package android.graphics; -import com.android.layoutlib.api.ResourceDensity; +import com.android.ide.common.rendering.api.ResourceDensity; import com.android.layoutlib.bridge.Bridge; import android.content.res.AssetManager; diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index fe201c1..d69abc9 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -16,7 +16,7 @@ package android.graphics; -import com.android.layoutlib.api.ResourceDensity; +import com.android.ide.common.rendering.api.ResourceDensity; import com.android.layoutlib.bridge.impl.DelegateManager; import android.graphics.Bitmap.Config; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index c30e8e4..5f40854 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -16,15 +16,17 @@ package com.android.layoutlib.bridge; -import com.android.layoutlib.api.Capability; -import com.android.layoutlib.api.LayoutBridge; -import com.android.layoutlib.api.LayoutLog; -import com.android.layoutlib.api.SceneParams; -import com.android.layoutlib.api.SceneResult; -import com.android.layoutlib.api.SceneResult.SceneStatus; +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.LayoutLog; +import com.android.ide.common.rendering.api.Params; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; import com.android.layoutlib.bridge.android.BridgeAssetManager; import com.android.layoutlib.bridge.impl.FontLoader; -import com.android.layoutlib.bridge.impl.LayoutSceneImpl; +import com.android.layoutlib.bridge.impl.RenderSessionImpl; import com.android.ninepatch.NinePatchChunk; import com.android.tools.layoutlib.create.MethodAdapter; import com.android.tools.layoutlib.create.OverrideMethod; @@ -33,6 +35,7 @@ import android.graphics.Bitmap; import android.graphics.Typeface_Delegate; import android.os.Looper; +import java.io.File; import java.lang.ref.SoftReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -47,7 +50,7 @@ import java.util.concurrent.locks.ReentrantLock; *

To use this bridge, simply instantiate an object of type {@link Bridge} and call * {@link #createScene(SceneParams)} */ -public final class Bridge extends LayoutBridge { +public final class Bridge extends com.android.ide.common.rendering.api.Bridge { public static class StaticMethodNotImplementedException extends RuntimeException { private static final long serialVersionUID = 1L; @@ -169,7 +172,7 @@ public final class Bridge extends LayoutBridge { @Override public int getApiLevel() { - return LayoutBridge.API_CURRENT; + return com.android.ide.common.rendering.api.Bridge.API_CURRENT; } @Override @@ -179,10 +182,10 @@ public final class Bridge extends LayoutBridge { /* * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutLibBridge#init(java.lang.String, java.util.Map) + * @see com.android.layoutlib.api.ILayoutLibBridge#init(java.io.File, java.util.Map) */ @Override - public boolean init(String fontOsLocation, Map> enumValueMap) { + public boolean init(File fontLocation, Map> enumValueMap) { sEnumValueMap = enumValueMap; // don't use EnumSet.allOf(), because the bridge doesn't come with its specific version @@ -229,7 +232,7 @@ public final class Bridge extends LayoutBridge { } // load the fonts. - FontLoader fontLoader = FontLoader.create(fontOsLocation); + FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath()); if (fontLoader != null) { Typeface_Delegate.init(fontLoader); } else { @@ -299,10 +302,10 @@ public final class Bridge extends LayoutBridge { * @since 5 */ @Override - public BridgeLayoutScene createScene(SceneParams params) { + public RenderSession createSession(Params params) { try { - SceneResult lastResult = SceneStatus.SUCCESS.createResult(); - LayoutSceneImpl scene = new LayoutSceneImpl(params); + Result lastResult = SUCCESS.createResult(); + RenderSessionImpl scene = new RenderSessionImpl(params); try { prepareThread(); lastResult = scene.init(params.getTimeout()); @@ -317,15 +320,15 @@ public final class Bridge extends LayoutBridge { cleanupThread(); } - return new BridgeLayoutScene(scene, lastResult); + 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 BridgeLayoutScene(null, - SceneStatus.ERROR_UNKNOWN.createResult(t2.getMessage(), t2)); + return new BridgeRenderSession(null, + ERROR_UNKNOWN.createResult(t2.getMessage(), t2)); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java deleted file mode 100644 index f43559f..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.layoutlib.bridge; - -import com.android.layoutlib.api.IXmlPullParser; -import com.android.layoutlib.api.LayoutScene; -import com.android.layoutlib.api.SceneParams; -import com.android.layoutlib.api.SceneResult; -import com.android.layoutlib.api.ViewInfo; -import com.android.layoutlib.bridge.impl.LayoutSceneImpl; - -import android.view.View; -import android.view.ViewGroup; - -import java.awt.image.BufferedImage; -import java.util.Map; - -/** - * An implementation of {@link LayoutScene}. - * - * This is a pretty basic class that does almost nothing. All of the work is done in - * {@link LayoutSceneImpl}. - * - */ -public class BridgeLayoutScene extends LayoutScene { - - private final LayoutSceneImpl mScene; - private SceneResult mLastResult; - - @Override - public SceneResult getResult() { - return mLastResult; - } - - @Override - public BufferedImage getImage() { - return mScene.getImage(); - } - - @Override - public ViewInfo getRootView() { - return mScene.getViewInfo(); - } - - @Override - public Map getDefaultViewPropertyValues(Object viewObject) { - return mScene.getDefaultViewPropertyValues(viewObject); - } - - @Override - public SceneResult render(long timeout) { - try { - Bridge.prepareThread(); - mLastResult = mScene.acquire(timeout); - if (mLastResult.isSuccess()) { - mLastResult = mScene.render(); - } - } finally { - mScene.release(); - Bridge.cleanupThread(); - } - - return mLastResult; - } - - @Override - public SceneResult animate(Object targetObject, String animationName, - boolean isFrameworkAnimation, IAnimationListener listener) { - try { - Bridge.prepareThread(); - mLastResult = mScene.acquire(SceneParams.DEFAULT_TIMEOUT); - if (mLastResult.isSuccess()) { - mLastResult = mScene.animate(targetObject, animationName, isFrameworkAnimation, - listener); - } - } finally { - mScene.release(); - Bridge.cleanupThread(); - } - - return mLastResult; - } - - @Override - public SceneResult insertChild(Object parentView, IXmlPullParser childXml, int index, - IAnimationListener listener) { - if (parentView instanceof ViewGroup == false) { - throw new IllegalArgumentException("parentView is not a ViewGroup"); - } - - try { - Bridge.prepareThread(); - mLastResult = mScene.acquire(SceneParams.DEFAULT_TIMEOUT); - if (mLastResult.isSuccess()) { - mLastResult = mScene.insertChild((ViewGroup) parentView, childXml, index, listener); - } - } finally { - mScene.release(); - Bridge.cleanupThread(); - } - - return mLastResult; - } - - - @Override - public SceneResult moveChild(Object parentView, Object childView, int index, - Map 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 = mScene.acquire(SceneParams.DEFAULT_TIMEOUT); - if (mLastResult.isSuccess()) { - mLastResult = mScene.moveChild((ViewGroup) parentView, (View) childView, index, - layoutParams, listener); - } - } finally { - mScene.release(); - Bridge.cleanupThread(); - } - - return mLastResult; - } - - @Override - public SceneResult removeChild(Object childView, IAnimationListener listener) { - if (childView instanceof View == false) { - throw new IllegalArgumentException("childView is not a View"); - } - - try { - Bridge.prepareThread(); - mLastResult = mScene.acquire(SceneParams.DEFAULT_TIMEOUT); - if (mLastResult.isSuccess()) { - mLastResult = mScene.removeChild((View) childView, listener); - } - } finally { - mScene.release(); - Bridge.cleanupThread(); - } - - return mLastResult; - } - - @Override - public void dispose() { - } - - /*package*/ BridgeLayoutScene(LayoutSceneImpl scene, SceneResult lastResult) { - mScene = scene; - mScene.setScene(this); - mLastResult = lastResult; - } -} 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..a09524c --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -0,0 +1,175 @@ +/* + * 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.Params; +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.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 ViewInfo getRootView() { + return mSession.getViewInfo(); + } + + @Override + public Map getDefaultProperties(Object viewObject) { + return mSession.getDefaultProperties(viewObject); + } + + @Override + public Result render(long timeout) { + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(timeout); + if (mLastResult.isSuccess()) { + mLastResult = mSession.render(); + } + } 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(Params.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(Params.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 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(Params.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(Params.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; + mSession.setScene(this); + mLastResult = lastResult; + } +} 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 index f484e7a..7269227 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -16,9 +16,9 @@ package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.ResourceValue; -import com.android.layoutlib.api.StyleResourceValue; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.impl.Stack; @@ -105,11 +105,11 @@ public final class BridgeContext extends Activity { * @param themeName The name of the theme to use. * @param projectResources the resources of the project. The map contains (String, map) pairs * where the string is the type of the resource reference used in the layout file, and the - * map contains (String, {@link IResourceValue}) pairs where the key is the resource name, + * map contains (String, {@link }) pairs where the key is the resource name, * and the value is the resource value. * @param frameworkResources the framework resources. The map contains (String, map) pairs * where the string is the type of the resource reference used in the layout file, and the map - * contains (String, {@link IResourceValue}) pairs where the key is the resource name, and the + * contains (String, {@link ResourceValue}) pairs where the key is the resource name, and the * value is the resource value. * @param styleInheritanceMap * @param projectCallback @@ -321,7 +321,7 @@ public final class BridgeContext extends Activity { isPlatformFile = parser.isPlatformFile(); - Object key = parser.getViewKey(); + Object key = parser.getViewCookie(); if (key != null) { defaultPropMap = mDefaultPropMaps.get(key); if (defaultPropMap == null) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java index dbf83e7..1b59621 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java @@ -16,8 +16,8 @@ package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.ResourceValue; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; @@ -217,14 +217,14 @@ public final class BridgeInflater extends LayoutInflater { BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs; // get the view key - Object viewKey = parser.getViewKey(); + Object viewKey = parser.getViewCookie(); // if there's no view key and the depth is 1 (ie this is the first tag), // look for a previous parser in the context, and check if this one has a viewkey. if (viewKey == null && parser.getDepth() == 1) { BridgeXmlBlockParser previousParser = bc.getPreviousParser(); if (previousParser != null) { - viewKey = previousParser.getViewKey(); + viewKey = previousParser.getViewCookie(); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java index c1e2046..2ea5281 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java @@ -16,8 +16,8 @@ package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.ResourceValue; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.impl.ResourceHelper; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java index fcbf5fa..6ae7e03 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java @@ -16,9 +16,9 @@ package com.android.layoutlib.bridge.android; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.internal.util.XmlUtils; -import com.android.layoutlib.api.ResourceValue; -import com.android.layoutlib.api.StyleResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.impl.ResourceHelper; @@ -66,7 +66,7 @@ public final class BridgeTypedArray extends TypedArray { } /** - * Seals the array after all calls to {@link #bridgeSetValue(int, String, IResourceValue)} have + * Seals the array after all calls to {@link #bridgeSetValue(int, String, ResourceValue)} have * been done. *

This allows to compute the list of non default values, permitting * {@link #getIndexCount()} to return the proper value. 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 index 73bee96..38800da 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java @@ -16,7 +16,8 @@ package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.IXmlPullParser; + +import com.android.ide.common.rendering.api.ILayoutPullParser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -65,17 +66,17 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return mPlatformFile; } - public IXmlPullParser getParser(String layoutName) { - if (mParser instanceof IXmlPullParser) { - return ((IXmlPullParser)mParser).getParser(layoutName); + public ILayoutPullParser getParser(String layoutName) { + if (mParser instanceof ILayoutPullParser) { + return ((ILayoutPullParser)mParser).getParser(layoutName); } return null; } - public Object getViewKey() { - if (mParser instanceof IXmlPullParser) { - return ((IXmlPullParser)mParser).getViewKey(); + public Object getViewCookie() { + if (mParser instanceof ILayoutPullParser) { + return ((ILayoutPullParser)mParser).getViewCookie(); } return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlPullAttributes.java index 92b98e5..ba45217 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlPullAttributes.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlPullAttributes.java @@ -16,7 +16,7 @@ package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.ResourceValue; +import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java index 4ee813c..1651d6a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java @@ -16,10 +16,10 @@ package com.android.layoutlib.bridge.impl; -import com.android.layoutlib.api.LayoutScene; -import com.android.layoutlib.api.SceneResult; -import com.android.layoutlib.api.LayoutScene.IAnimationListener; -import com.android.layoutlib.api.SceneResult.SceneStatus; +import com.android.ide.common.rendering.api.IAnimationListener; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.Result.Status; import com.android.layoutlib.bridge.Bridge; import android.animation.ValueAnimator; @@ -57,18 +57,19 @@ public abstract class AnimationThread extends Thread { } } - private final LayoutSceneImpl mScene; + private final RenderSessionImpl mSession; Queue mQueue = new LinkedList(); private final IAnimationListener mListener; - public AnimationThread(LayoutSceneImpl scene, String threadName, IAnimationListener listener) { + public AnimationThread(RenderSessionImpl scene, String threadName, + IAnimationListener listener) { super(threadName); - mScene = scene; + mSession = scene; mListener = listener; } - public abstract SceneResult preAnimation(); + public abstract Result preAnimation(); public abstract void postAnimation(); @Override @@ -87,13 +88,13 @@ public abstract class AnimationThread extends Thread { }); // call out to the pre-animation work, which should start an animation or more. - SceneResult result = preAnimation(); + Result result = preAnimation(); if (result.isSuccess() == false) { mListener.done(result); } // loop the animation - LayoutScene scene = mScene.getScene(); + RenderSession session = mSession.getSession(); do { // check early. if (mListener.isCanceled()) { @@ -123,7 +124,7 @@ public abstract class AnimationThread extends Thread { } // ready to do the work, acquire the scene. - result = mScene.acquire(250); + result = mSession.acquire(250); if (result.isSuccess() == false) { mListener.done(result); return; @@ -138,15 +139,15 @@ public abstract class AnimationThread extends Thread { } bundle.mTarget.handleMessage(bundle.mMessage); - if (mScene.render().isSuccess()) { - mListener.onNewFrame(scene); + if (mSession.render().isSuccess()) { + mListener.onNewFrame(session); } } finally { - mScene.release(); + mSession.release(); } } while (mListener.isCanceled() == false && mQueue.size() > 0); - mListener.done(SceneStatus.SUCCESS.createResult()); + mListener.done(Status.SUCCESS.createResult()); } finally { postAnimation(); Handler_Delegate.setCallback(null); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java deleted file mode 100644 index 24cf380..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java +++ /dev/null @@ -1,1157 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.layoutlib.bridge.impl; - -import static com.android.layoutlib.api.SceneResult.SceneStatus.ERROR_LOCK_INTERRUPTED; -import static com.android.layoutlib.api.SceneResult.SceneStatus.ERROR_TIMEOUT; -import static com.android.layoutlib.api.SceneResult.SceneStatus.SUCCESS; - -import com.android.internal.util.XmlUtils; -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.IXmlPullParser; -import com.android.layoutlib.api.LayoutBridge; -import com.android.layoutlib.api.LayoutScene; -import com.android.layoutlib.api.ResourceDensity; -import com.android.layoutlib.api.ResourceValue; -import com.android.layoutlib.api.SceneParams; -import com.android.layoutlib.api.SceneResult; -import com.android.layoutlib.api.StyleResourceValue; -import com.android.layoutlib.api.ViewInfo; -import com.android.layoutlib.api.LayoutScene.IAnimationListener; -import com.android.layoutlib.api.SceneParams.RenderingMode; -import com.android.layoutlib.api.SceneResult.SceneStatus; -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.BridgeInflater; -import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; -import com.android.layoutlib.bridge.android.BridgeWindow; -import com.android.layoutlib.bridge.android.BridgeWindowSession; -import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; - -import android.animation.Animator; -import android.animation.AnimatorInflater; -import android.animation.LayoutTransition; -import android.app.Fragment_Delegate; -import android.graphics.Bitmap; -import android.graphics.Bitmap_Delegate; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; -import android.view.View.AttachInfo; -import android.view.View.MeasureSpec; -import android.view.ViewGroup.LayoutParams; -import android.widget.FrameLayout; -import android.widget.TabHost; -import android.widget.TabWidget; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Class managing a layout "scene". - * - * A scene is a stateful representation of a layout file. It is initialized with data coming through - * the {@link LayoutBridge} API to inflate the layout. Further actions and rendering can then - * be done on the layout. - * - */ -public class LayoutSceneImpl { - - private static final int DEFAULT_TITLE_BAR_HEIGHT = 25; - private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; - - /** - * 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 SceneParams mParams; - - // scene state - private LayoutScene mScene; - private BridgeContext mContext; - private BridgeXmlBlockParser mBlockParser; - private BridgeInflater mInflater; - private StyleResourceValue mCurrentTheme; - private int mScreenOffset; - private ResourceValue mWindowBackground; - private FrameLayout mViewRoot; - private Canvas mCanvas; - private int mMeasuredScreenWidth = -1; - private int mMeasuredScreenHeight = -1; - - // information being returned through the API - private BufferedImage mImage; - private ViewInfo mViewInfo; - - 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. - *

- * This must be followed by a call to {@link LayoutSceneImpl#init()}, which act as a - * call to {@link LayoutSceneImpl#acquire(long)} - * - * @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams) - */ - public LayoutSceneImpl(SceneParams params) { - // copy the params. - mParams = new SceneParams(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 SceneResult init(long timeout) { - // acquire the lock. if the result is null, lock was just acquired, otherwise, return - // the result. - SceneResult result = acquireLock(timeout); - if (result != null) { - return result; - } - - Bridge.setLog(mParams.getLog()); - - // setup the display Metrics. - DisplayMetrics metrics = new DisplayMetrics(); - metrics.densityDpi = mParams.getDensity(); - metrics.density = mParams.getDensity() / (float) DisplayMetrics.DENSITY_DEFAULT; - metrics.scaledDensity = metrics.density; - metrics.widthPixels = mParams.getScreenWidth(); - metrics.heightPixels = mParams.getScreenHeight(); - metrics.xdpi = mParams.getXdpi(); - metrics.ydpi = mParams.getYdpi(); - - // find the current theme and compute the style inheritance map - Map styleParentMap = - new HashMap(); - - mCurrentTheme = computeStyleMaps(mParams.getThemeName(), mParams.getIsProjectTheme(), - mParams.getProjectResources().get(BridgeConstants.RES_STYLE), - mParams.getFrameworkResources().get(BridgeConstants.RES_STYLE), styleParentMap); - - // build the context - mContext = new BridgeContext(mParams.getProjectKey(), metrics, mCurrentTheme, - mParams.getProjectResources(), mParams.getFrameworkResources(), - styleParentMap, mParams.getProjectCallback()); - - // set the current rendering context - sCurrentContext = mContext; - - // make sure the Resources object references the context (and other objects) for this - // scene - mContext.initResources(); - - // get the screen offset and window-background resource - mWindowBackground = null; - mScreenOffset = 0; - if (mCurrentTheme != null && mParams.isCustomBackgroundEnabled() == false) { - mWindowBackground = mContext.findItemInStyle(mCurrentTheme, "windowBackground"); - mWindowBackground = mContext.resolveResValue(mWindowBackground); - - mScreenOffset = getScreenOffset(mParams.getFrameworkResources(), mCurrentTheme, - mContext); - } - - // build the inflater and parser. - mInflater = new BridgeInflater(mContext, mParams.getProjectCallback()); - mContext.setBridgeInflater(mInflater); - mInflater.setFactory2(mContext); - - mBlockParser = new BridgeXmlBlockParser(mParams.getLayoutDescription(), - mContext, false /* platformResourceFlag */); - - return SceneStatus.SUCCESS.createResult(); - } - - /** - * Prepares the scene for action. - *

- * 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 SceneResult#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 SceneResult 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. - SceneResult result = acquireLock(timeout); - if (result != null) { - return result; - } - - // make sure the Resources object references the context (and other objects) for this - // scene - mContext.initResources(); - sCurrentContext = mContext; - Bridge.setLog(mParams.getLog()); - - return SUCCESS.createResult(); - } - - /** - * Acquire the lock so that the scene can be acted upon. - *

- * This returns null if the lock was just acquired, otherwise it returns - * {@link SceneResult#SUCCESS} if the lock already belonged to that thread, or another - * instance (see {@link SceneResult#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 SceneResult 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()) { - // Make sure to remove static references, otherwise we could not unload the lib - mContext.disposeResources(); - Bridge.setLog(null); - sCurrentContext = null; - - lock.unlock(); - } - } - - /** - * Inflates the layout. - *

- * {@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 SceneResult inflate() { - checkLock(); - - try { - - mViewRoot = new FrameLayout(mContext); - - // Sets the project callback (custom view loader) to the fragment delegate so that - // it can instantiate the custom Fragment. - Fragment_Delegate.setProjectCallback(mParams.getProjectCallback()); - - View view = mInflater.inflate(mBlockParser, mViewRoot); - - // post-inflate process. For now this supports TabHost/TabWidget - postInflateProcess(view, mParams.getProjectCallback()); - - Fragment_Delegate.setProjectCallback(null); - - // set the AttachInfo on the root view. - AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(), - new Handler(), null); - info.mHasWindowFocus = true; - info.mWindowVisibility = View.VISIBLE; - info.mInTouchMode = false; // this is so that we can display selections. - mViewRoot.dispatchAttachedToWindow(info, 0); - - // get the background drawable - if (mWindowBackground != null) { - Drawable d = ResourceHelper.getDrawable(mWindowBackground, - mContext, true /* isFramework */); - mViewRoot.setBackgroundDrawable(d); - } - - return SceneStatus.SUCCESS.createResult(); - } catch (PostInflateException e) { - return SceneStatus.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(); - } - - // log it - mParams.getLog().error("Scene inflate failed", t); - - return SceneStatus.ERROR_INFLATION.createResult(t.getMessage(), t); - } - } - - /** - * Renders the scene. - *

- * {@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 SceneParams#getRenderingMode() - * @see LayoutScene#render(long) - */ - public SceneResult render() { - checkLock(); - - try { - if (mViewRoot == null) { - return SceneStatus.ERROR_NOT_INFLATED.createResult(); - } - // measure the views - int w_spec, h_spec; - - RenderingMode renderingMode = mParams.getRenderingMode(); - - // only do the screen measure when needed. - boolean newRenderSize = false; - if (mMeasuredScreenWidth == -1) { - newRenderSize = true; - mMeasuredScreenWidth = mParams.getScreenWidth(); - mMeasuredScreenHeight = mParams.getScreenHeight(); - - if (renderingMode != RenderingMode.NORMAL) { - // measure the full size needed by the layout. - w_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenWidth, - renderingMode.isHorizExpand() ? - MeasureSpec.UNSPECIFIED // this lets us know the actual needed size - : MeasureSpec.EXACTLY); - h_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenHeight - mScreenOffset, - renderingMode.isVertExpand() ? - MeasureSpec.UNSPECIFIED // this lets us know the actual needed size - : MeasureSpec.EXACTLY); - mViewRoot.measure(w_spec, h_spec); - - if (renderingMode.isHorizExpand()) { - int neededWidth = mViewRoot.getChildAt(0).getMeasuredWidth(); - if (neededWidth > mMeasuredScreenWidth) { - mMeasuredScreenWidth = neededWidth; - } - } - - if (renderingMode.isVertExpand()) { - int neededHeight = mViewRoot.getChildAt(0).getMeasuredHeight(); - if (neededHeight > mMeasuredScreenHeight - mScreenOffset) { - mMeasuredScreenHeight = neededHeight + mScreenOffset; - } - } - } - } - - // remeasure with the size we need - // This must always be done before the call to layout - w_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenWidth, MeasureSpec.EXACTLY); - h_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenHeight - mScreenOffset, - MeasureSpec.EXACTLY); - mViewRoot.measure(w_spec, h_spec); - - // now do the layout. - mViewRoot.layout(0, mScreenOffset, mMeasuredScreenWidth, mMeasuredScreenHeight); - - // draw the views - // create the BufferedImage into which the layout will be rendered. - if (newRenderSize || mCanvas == null) { - if (mParams.getImageFactory() != null) { - mImage = mParams.getImageFactory().getImage(mMeasuredScreenWidth, - mMeasuredScreenHeight - mScreenOffset); - } else { - mImage = new BufferedImage(mMeasuredScreenWidth, - mMeasuredScreenHeight - mScreenOffset, BufferedImage.TYPE_INT_ARGB); - } - - if (mParams.isCustomBackgroundEnabled()) { - Graphics2D gc = mImage.createGraphics(); - gc.setColor(new Color(mParams.getCustomBackgroundColor(), true)); - gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight - mScreenOffset); - gc.dispose(); - } - - // create an Android bitmap around the BufferedImage - Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage, - true /*isMutable*/, - ResourceDensity.getEnum(mParams.getDensity())); - - // create a Canvas around the Android bitmap - mCanvas = new Canvas(bitmap); - mCanvas.setDensity(mParams.getDensity()); - } - - mViewRoot.draw(mCanvas); - - mViewInfo = visit(((ViewGroup)mViewRoot).getChildAt(0), mContext); - - // success! - return SceneStatus.SUCCESS.createResult(); - } catch (Throwable e) { - // get the real cause of the exception. - Throwable t = e; - while (t.getCause() != null) { - t = t.getCause(); - } - - // log it - mParams.getLog().error("Scene Render failed", t); - - return SceneStatus.ERROR_UNKNOWN.createResult(t.getMessage(), t); - } - } - - /** - * Animate an object - *

- * {@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 LayoutScene#animate(Object, String, boolean, IAnimationListener) - */ - public SceneResult animate(Object targetObject, String animationName, - boolean isFrameworkAnimation, IAnimationListener listener) { - checkLock(); - - // find the animation file. - ResourceValue animationResource = null; - int animationId = 0; - if (isFrameworkAnimation) { - animationResource = mContext.getFrameworkResource("anim", animationName); - if (animationResource != null) { - animationId = Bridge.getResourceValue("anim", animationName); - } - } else { - animationResource = mContext.getProjectResource("anim", animationName); - if (animationResource != null) { - animationId = mContext.getProjectCallback().getResourceValue("anim", animationName); - } - } - - if (animationResource != null) { - try { - Animator anim = AnimatorInflater.loadAnimator(mContext, animationId); - if (anim != null) { - anim.setTarget(targetObject); - - new PlayAnimationThread(anim, this, animationName, listener).start(); - - return SceneStatus.SUCCESS.createResult(); - } - } catch (Exception e) { - // get the real cause of the exception. - Throwable t = e; - while (t.getCause() != null) { - t = t.getCause(); - } - - return SceneStatus.ERROR_UNKNOWN.createResult(t.getMessage(), t); - } - } - - return SceneStatus.ERROR_ANIM_NOT_FOUND.createResult(); - } - - /** - * Insert a new child into an existing parent. - *

- * {@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 LayoutScene#insertChild(Object, IXmlPullParser, int, IAnimationListener) - */ - public SceneResult insertChild(final ViewGroup parentView, IXmlPullParser childXml, - final int index, IAnimationListener listener) { - checkLock(); - - // create a block parser for the XML - BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(childXml, mContext, - 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*/); - - invalidateRenderingSize(); - - if (listener != null) { - new AnimationThread(this, "insertChild", listener) { - - @Override - public SceneResult 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 SceneStatus.SUCCESS.createResult(child); - } - - // add it to the parentView in the correct location - SceneResult result = addView(parentView, child, index); - if (result.isSuccess() == false) { - return result; - } - - result = render(); - 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 SceneResult with {@link SceneStatus#SUCCESS} or - * {@link SceneStatus#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support - * adding views. - */ - private SceneResult addView(ViewGroup parent, View view, int index) { - try { - parent.addView(view, index); - return SceneStatus.SUCCESS.createResult(); - } catch (UnsupportedOperationException e) { - // looks like this is a view class that doesn't support children manipulation! - return SceneStatus.ERROR_VIEWGROUP_NO_CHILDREN.createResult(); - } - } - - /** - * Moves a view to a new parent at a given location - *

- * {@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 LayoutScene#moveChild(Object, Object, int, Map, IAnimationListener) - */ - public SceneResult moveChild(final ViewGroup parentView, final View childView, final int index, - Map layoutParamsMap, IAnimationListener listener) { - checkLock(); - - invalidateRenderingSize(); - - LayoutParams layoutParams = null; - if (layoutParamsMap != null) { - // need to create a new LayoutParams object for the new parent. - layoutParams = parentView.generateLayoutParams( - new BridgeLayoutParamsMapAttributes(layoutParamsMap)); - } - - if (listener != null) { - final LayoutParams params = layoutParams; - new AnimationThread(this, "moveChild", listener) { - - @Override - public SceneResult preAnimation() { - parentView.setLayoutTransition(new LayoutTransition()); - return moveView(parentView, childView, index, params); - } - - @Override - public void postAnimation() { - parentView.setLayoutTransition(null); - } - }.start(); - - // always return success since the real status will come through the listener. - return SceneStatus.SUCCESS.createResult(layoutParams); - } - - SceneResult result = moveView(parentView, childView, index, layoutParams); - if (result.isSuccess() == false) { - return result; - } - - result = render(); - 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 parent the new parent - * @param view 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 SceneResult with {@link SceneStatus#SUCCESS} or - * {@link SceneStatus#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support - * adding views. - */ - private SceneResult moveView(ViewGroup parent, View view, int index, LayoutParams params) { - try { - ViewGroup previousParent = (ViewGroup) view.getParent(); - previousParent.removeView(view); - - // add it to the parentView in the correct location - - if (params != null) { - parent.addView(view, index, params); - } else { - parent.addView(view, index); - } - - return SceneStatus.SUCCESS.createResult(); - } catch (UnsupportedOperationException e) { - // looks like this is a view class that doesn't support children manipulation! - return SceneStatus.ERROR_VIEWGROUP_NO_CHILDREN.createResult(); - } - } - - /** - * Removes a child from its current parent. - *

- * {@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 LayoutScene#removeChild(Object, IAnimationListener) - */ - public SceneResult removeChild(final View childView, IAnimationListener listener) { - checkLock(); - - invalidateRenderingSize(); - - final ViewGroup parent = (ViewGroup) childView.getParent(); - - if (listener != null) { - new AnimationThread(this, "moveChild", listener) { - - @Override - public SceneResult 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 SceneStatus.SUCCESS.createResult(); - } - - SceneResult result = removeView(parent, childView); - if (result.isSuccess() == false) { - return result; - } - - return render(); - } - - /** - * Removes a given view from its current parent. - * - * @param view the view to remove from its parent - * - * @return a SceneResult with {@link SceneStatus#SUCCESS} or - * {@link SceneStatus#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support - * adding views. - */ - private SceneResult removeView(ViewGroup parent, View view) { - try { - parent.removeView(view); - return SceneStatus.SUCCESS.createResult(); - } catch (UnsupportedOperationException e) { - // looks like this is a view class that doesn't support children manipulation! - return SceneStatus.ERROR_VIEWGROUP_NO_CHILDREN.createResult(); - } - } - - /** - * 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. - */ - private 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"); - } - } - - - /** - * Compute style information from the given list of style for the project and framework. - * @param themeName the name of the current theme. In order to differentiate project and - * platform themes sharing the same name, all project themes must be prepended with - * a '*' character. - * @param isProjectTheme Is this a project theme - * @param inProjectStyleMap the project style map - * @param inFrameworkStyleMap the framework style map - * @param outInheritanceMap the map of style inheritance. This is filled by the method - * @return the {@link IStyleResourceValue} matching themeName - */ - private StyleResourceValue computeStyleMaps( - String themeName, boolean isProjectTheme, Map inProjectStyleMap, Map inFrameworkStyleMap, - Map outInheritanceMap) { - - if (inProjectStyleMap != null && inFrameworkStyleMap != null) { - // first, get the theme - ResourceValue theme = null; - - // project theme names have been prepended with a * - if (isProjectTheme) { - theme = inProjectStyleMap.get(themeName); - } else { - theme = inFrameworkStyleMap.get(themeName); - } - - if (theme instanceof StyleResourceValue) { - // compute the inheritance map for both the project and framework styles - computeStyleInheritance(inProjectStyleMap.values(), inProjectStyleMap, - inFrameworkStyleMap, outInheritanceMap); - - // Compute the style inheritance for the framework styles/themes. - // Since, for those, the style parent values do not contain 'android:' - // we want to force looking in the framework style only to avoid using - // similarly named styles from the project. - // To do this, we pass null in lieu of the project style map. - computeStyleInheritance(inFrameworkStyleMap.values(), null /*inProjectStyleMap */, - inFrameworkStyleMap, outInheritanceMap); - - return (StyleResourceValue)theme; - } - } - - return null; - } - - /** - * Compute the parent style for all the styles in a given list. - * @param styles the styles for which we compute the parent. - * @param inProjectStyleMap the map of project styles. - * @param inFrameworkStyleMap the map of framework styles. - * @param outInheritanceMap the map of style inheritance. This is filled by the method. - */ - private void computeStyleInheritance(Collection styles, - Map inProjectStyleMap, - Map inFrameworkStyleMap, - Map outInheritanceMap) { - for (ResourceValue value : styles) { - if (value instanceof StyleResourceValue) { - StyleResourceValue style = (StyleResourceValue)value; - StyleResourceValue parentStyle = null; - - // first look for a specified parent. - String parentName = style.getParentStyle(); - - // no specified parent? try to infer it from the name of the style. - if (parentName == null) { - parentName = getParentName(value.getName()); - } - - if (parentName != null) { - parentStyle = getStyle(parentName, inProjectStyleMap, inFrameworkStyleMap); - - if (parentStyle != null) { - outInheritanceMap.put(style, parentStyle); - } - } - } - } - } - - /** - * Searches for and returns the {@link IStyleResourceValue} from a given name. - *

The format of the name can be: - *

- * @param parentName the name of the style. - * @param inProjectStyleMap the project style map. Can be null - * @param inFrameworkStyleMap the framework style map. - * @return The matching {@link IStyleResourceValue} object or null if not found. - */ - private StyleResourceValue getStyle(String parentName, - Map inProjectStyleMap, - Map inFrameworkStyleMap) { - boolean frameworkOnly = false; - - String name = parentName; - - // remove the useless @ if it's there - if (name.startsWith(BridgeConstants.PREFIX_RESOURCE_REF)) { - name = name.substring(BridgeConstants.PREFIX_RESOURCE_REF.length()); - } - - // check for framework identifier. - if (name.startsWith(BridgeConstants.PREFIX_ANDROID)) { - frameworkOnly = true; - name = name.substring(BridgeConstants.PREFIX_ANDROID.length()); - } - - // at this point we could have the format /. we want only the name as long as - // the type is style. - if (name.startsWith(BridgeConstants.REFERENCE_STYLE)) { - name = name.substring(BridgeConstants.REFERENCE_STYLE.length()); - } else if (name.indexOf('/') != -1) { - return null; - } - - ResourceValue parent = null; - - // if allowed, search in the project resources. - if (frameworkOnly == false && inProjectStyleMap != null) { - parent = inProjectStyleMap.get(name); - } - - // if not found, then look in the framework resources. - if (parent == null) { - parent = inFrameworkStyleMap.get(name); - } - - // make sure the result is the proper class type and return it. - if (parent instanceof StyleResourceValue) { - return (StyleResourceValue)parent; - } - - assert false; - mParams.getLog().error(null, - String.format("Unable to resolve parent style name: %s", parentName)); - - return null; - } - - /** - * Computes the name of the parent style, or null if the style is a root style. - */ - private String getParentName(String styleName) { - int index = styleName.lastIndexOf('.'); - if (index != -1) { - return styleName.substring(0, index); - } - - return null; - } - - /** - * Returns the top screen offset. This depends on whether the current theme defines the user - * of the title and status bars. - * @param frameworkResources The framework resources - * @param currentTheme The current theme - * @param context The context - * @return the pixel height offset - */ - private int getScreenOffset(Map> frameworkResources, - StyleResourceValue currentTheme, BridgeContext context) { - int offset = 0; - - // get the title bar flag from the current theme. - ResourceValue value = context.findItemInStyle(currentTheme, "windowNoTitle"); - - // because it may reference something else, we resolve it. - value = context.resolveResValue(value); - - // if there's a value and it's true (default is false) - if (value == null || value.getValue() == null || - XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { - // default size of the window title bar - int defaultOffset = DEFAULT_TITLE_BAR_HEIGHT; - - // get value from the theme. - value = context.findItemInStyle(currentTheme, "windowTitleSize"); - - // resolve it - value = context.resolveResValue(value); - - if (value != null) { - // get the numerical value, if available - TypedValue typedValue = ResourceHelper.getValue(value.getValue()); - if (typedValue != null) { - // compute the pixel value based on the display metrics - defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics); - } - } - - offset += defaultOffset; - } - - // get the fullscreen flag from the current theme. - value = context.findItemInStyle(currentTheme, "windowFullscreen"); - - // because it may reference something else, we resolve it. - value = context.resolveResValue(value); - - if (value == null || value.getValue() == null || - XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { - - // default value - int defaultOffset = DEFAULT_STATUS_BAR_HEIGHT; - - // get the real value, first the list of Dimensions from the framework map - Map dimens = frameworkResources.get(BridgeConstants.RES_DIMEN); - - // now get the value - value = dimens.get("status_bar_height"); - if (value != null) { - TypedValue typedValue = ResourceHelper.getValue(value.getValue()); - if (typedValue != null) { - // compute the pixel value based on the display metrics - defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics); - } - } - - // add the computed offset. - offset += defaultOffset; - } - - return offset; - - } - - /** - * Post process on a view hierachy that was just inflated. - *

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 ViewGroup) { - ViewGroup group = (ViewGroup)view; - final int count = group.getChildCount(); - for (int c = 0 ; c < count ; c++) { - View child = group.getChildAt(c); - postInflateProcess(child, projectCallback); - } - } - } - - /** - * Sets up a {@link TabHost} object. - * @param tabHost the TabHost to setup. - * @param projectCallback The project callback object to access the project R class. - * @throws PostInflateException - */ - private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback) - throws PostInflateException { - // look for the TabWidget, and the FrameLayout. They have their own specific names - View v = tabHost.findViewById(android.R.id.tabs); - - if (v == null) { - throw new PostInflateException( - "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); - } - - if ((v instanceof TabWidget) == false) { - throw new PostInflateException(String.format( - "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + - "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); - } - - v = tabHost.findViewById(android.R.id.tabcontent); - - if (v == null) { - // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) - throw new PostInflateException( - "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); - } - - if ((v instanceof FrameLayout) == false) { - throw new PostInflateException(String.format( - "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + - "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); - } - - FrameLayout content = (FrameLayout)v; - - // now process the content of the framelayout and dynamically create tabs for it. - final int count = content.getChildCount(); - - if (count == 0) { - throw new PostInflateException( - "The FrameLayout for the TabHost has no content. Rendering failed.\n"); - } - - // this must be called before addTab() so that the TabHost searches its TabWidget - // and FrameLayout. - tabHost.setup(); - - // for each child of the framelayout, add a new TabSpec - for (int i = 0 ; i < count ; i++) { - View child = content.getChildAt(i); - String tabSpec = String.format("tab_spec%d", i+1); - int id = child.getId(); - String[] resource = projectCallback.resolveResourceValue(id); - String name; - if (resource != null) { - name = resource[0]; // 0 is resource name, 1 is resource type. - } else { - name = String.format("Tab %d", i+1); // default name if id is unresolved. - } - tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id)); - } - } - - - /** - * Visits a View and its children and generate a {@link ViewInfo} containing the - * bounds of all the views. - * @param view the root View - * @param context the context. - */ - private ViewInfo visit(View view, BridgeContext context) { - if (view == null) { - return null; - } - - ViewInfo result = new ViewInfo(view.getClass().getName(), - context.getViewKey(view), - view.getLeft(), view.getTop(), view.getRight(), view.getBottom(), - view, view.getLayoutParams()); - - if (view instanceof ViewGroup) { - ViewGroup group = ((ViewGroup) view); - List children = new ArrayList(); - for (int i = 0; i < group.getChildCount(); i++) { - children.add(visit(group.getChildAt(i), context)); - } - result.setChildren(children); - } - - return result; - } - - private void invalidateRenderingSize() { - mMeasuredScreenWidth = mMeasuredScreenHeight = -1; - } - - public BufferedImage getImage() { - return mImage; - } - - public ViewInfo getViewInfo() { - return mViewInfo; - } - - public Map getDefaultViewPropertyValues(Object viewObject) { - return mContext.getDefaultPropMap(viewObject); - } - - public void setScene(LayoutScene scene) { - mScene = scene; - } - - public LayoutScene getScene() { - return mScene; - } -} 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 index 2e2c5f4..35e5987 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java @@ -16,9 +16,9 @@ package com.android.layoutlib.bridge.impl; -import com.android.layoutlib.api.SceneResult; -import com.android.layoutlib.api.LayoutScene.IAnimationListener; -import com.android.layoutlib.api.SceneResult.SceneStatus; +import com.android.ide.common.rendering.api.IAnimationListener; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.Result.Status; import android.animation.Animator; @@ -26,19 +26,19 @@ public class PlayAnimationThread extends AnimationThread { private final Animator mAnimator; - public PlayAnimationThread(Animator animator, LayoutSceneImpl scene, String animName, + public PlayAnimationThread(Animator animator, RenderSessionImpl scene, String animName, IAnimationListener listener) { super(scene, animName, listener); mAnimator = animator; } @Override - public SceneResult preAnimation() { + 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 SceneStatus.SUCCESS.createResult(); + return Status.SUCCESS.createResult(); } @Override 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..5f86c92 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -0,0 +1,1161 @@ +/* + * 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_LOCK_INTERRUPTED; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN; +import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; + +import com.android.ide.common.rendering.api.IAnimationListener; +import com.android.ide.common.rendering.api.ILayoutPullParser; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.Params; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.ResourceDensity; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.Params.RenderingMode; +import com.android.ide.common.rendering.api.Result.Status; +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.BridgeInflater; +import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; +import com.android.layoutlib.bridge.android.BridgeWindow; +import com.android.layoutlib.bridge.android.BridgeWindowSession; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; + +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.animation.LayoutTransition; +import android.app.Fragment_Delegate; +import android.graphics.Bitmap; +import android.graphics.Bitmap_Delegate; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.AttachInfo; +import android.view.View.MeasureSpec; +import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; +import android.widget.TabHost; +import android.widget.TabWidget; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 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 { + + private static final int DEFAULT_TITLE_BAR_HEIGHT = 25; + private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; + + /** + * 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 Params mParams; + + // scene state + private RenderSession mScene; + private BridgeContext mContext; + private BridgeXmlBlockParser mBlockParser; + private BridgeInflater mInflater; + private StyleResourceValue mCurrentTheme; + private int mScreenOffset; + private ResourceValue mWindowBackground; + private FrameLayout mViewRoot; + private Canvas mCanvas; + private int mMeasuredScreenWidth = -1; + private int mMeasuredScreenHeight = -1; + + // information being returned through the API + private BufferedImage mImage; + private ViewInfo mViewInfo; + + 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. + *

+ * This must 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(Params params) { + // copy the params. + mParams = new Params(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; + } + + Bridge.setLog(mParams.getLog()); + + // setup the display Metrics. + DisplayMetrics metrics = new DisplayMetrics(); + metrics.densityDpi = mParams.getDensity(); + metrics.density = mParams.getDensity() / (float) DisplayMetrics.DENSITY_DEFAULT; + metrics.scaledDensity = metrics.density; + metrics.widthPixels = mParams.getScreenWidth(); + metrics.heightPixels = mParams.getScreenHeight(); + metrics.xdpi = mParams.getXdpi(); + metrics.ydpi = mParams.getYdpi(); + + // find the current theme and compute the style inheritance map + Map styleParentMap = + new HashMap(); + + mCurrentTheme = computeStyleMaps(mParams.getThemeName(), mParams.isProjectTheme(), + mParams.getProjectResources().get(BridgeConstants.RES_STYLE), + mParams.getFrameworkResources().get(BridgeConstants.RES_STYLE), styleParentMap); + + // build the context + mContext = new BridgeContext(mParams.getProjectKey(), metrics, mCurrentTheme, + mParams.getProjectResources(), mParams.getFrameworkResources(), + styleParentMap, mParams.getProjectCallback()); + + // set the current rendering context + sCurrentContext = mContext; + + // make sure the Resources object references the context (and other objects) for this + // scene + mContext.initResources(); + + // get the screen offset and window-background resource + mWindowBackground = null; + mScreenOffset = 0; + if (mCurrentTheme != null && mParams.isBgColorOverridden() == false) { + mWindowBackground = mContext.findItemInStyle(mCurrentTheme, "windowBackground"); + mWindowBackground = mContext.resolveResValue(mWindowBackground); + + mScreenOffset = getScreenOffset(mParams.getFrameworkResources(), mCurrentTheme, + mContext); + } + + // build the inflater and parser. + mInflater = new BridgeInflater(mContext, mParams.getProjectCallback()); + mContext.setBridgeInflater(mInflater); + mInflater.setFactory2(mContext); + + mBlockParser = new BridgeXmlBlockParser(mParams.getLayoutDescription(), + mContext, false /* platformResourceFlag */); + + return SUCCESS.createResult(); + } + + /** + * Prepares the scene for action. + *

+ * 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; + } + + // make sure the Resources object references the context (and other objects) for this + // scene + mContext.initResources(); + sCurrentContext = mContext; + Bridge.setLog(mParams.getLog()); + + return SUCCESS.createResult(); + } + + /** + * Acquire the lock so that the scene can be acted upon. + *

+ * 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()) { + // Make sure to remove static references, otherwise we could not unload the lib + mContext.disposeResources(); + Bridge.setLog(null); + sCurrentContext = null; + + lock.unlock(); + } + } + + /** + * Inflates the layout. + *

+ * {@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 { + + mViewRoot = new FrameLayout(mContext); + + // Sets the project callback (custom view loader) to the fragment delegate so that + // it can instantiate the custom Fragment. + Fragment_Delegate.setProjectCallback(mParams.getProjectCallback()); + + View view = mInflater.inflate(mBlockParser, mViewRoot); + + // post-inflate process. For now this supports TabHost/TabWidget + postInflateProcess(view, mParams.getProjectCallback()); + + Fragment_Delegate.setProjectCallback(null); + + // set the AttachInfo on the root view. + AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(), + new Handler(), null); + info.mHasWindowFocus = true; + info.mWindowVisibility = View.VISIBLE; + info.mInTouchMode = false; // this is so that we can display selections. + mViewRoot.dispatchAttachedToWindow(info, 0); + + // get the background drawable + if (mWindowBackground != null) { + Drawable d = ResourceHelper.getDrawable(mWindowBackground, + mContext, true /* isFramework */); + mViewRoot.setBackgroundDrawable(d); + } + + return SUCCESS.createResult(); + } catch (PostInflateException e) { + return ERROR_INFLATION.createResult(e.getMessage(), e); + } catch (Throwable e) { + // get the real cause of the exception. + Throwable t = e; + while (t.getCause() != null) { + t = t.getCause(); + } + + // log it + mParams.getLog().error("Scene inflate failed", t); + + return ERROR_INFLATION.createResult(t.getMessage(), t); + } + } + + /** + * Renders the scene. + *

+ * {@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 SceneParams#getRenderingMode() + * @see LayoutScene#render(long) + */ + public Result render() { + checkLock(); + + try { + if (mViewRoot == null) { + return ERROR_NOT_INFLATED.createResult(); + } + // measure the views + int w_spec, h_spec; + + RenderingMode renderingMode = mParams.getRenderingMode(); + + // only do the screen measure when needed. + boolean newRenderSize = false; + if (mMeasuredScreenWidth == -1) { + newRenderSize = true; + mMeasuredScreenWidth = mParams.getScreenWidth(); + mMeasuredScreenHeight = mParams.getScreenHeight(); + + if (renderingMode != RenderingMode.NORMAL) { + // measure the full size needed by the layout. + w_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenWidth, + renderingMode.isHorizExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY); + h_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenHeight - mScreenOffset, + renderingMode.isVertExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY); + mViewRoot.measure(w_spec, h_spec); + + if (renderingMode.isHorizExpand()) { + int neededWidth = mViewRoot.getChildAt(0).getMeasuredWidth(); + if (neededWidth > mMeasuredScreenWidth) { + mMeasuredScreenWidth = neededWidth; + } + } + + if (renderingMode.isVertExpand()) { + int neededHeight = mViewRoot.getChildAt(0).getMeasuredHeight(); + if (neededHeight > mMeasuredScreenHeight - mScreenOffset) { + mMeasuredScreenHeight = neededHeight + mScreenOffset; + } + } + } + } + + // remeasure with the size we need + // This must always be done before the call to layout + w_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenWidth, MeasureSpec.EXACTLY); + h_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenHeight - mScreenOffset, + MeasureSpec.EXACTLY); + mViewRoot.measure(w_spec, h_spec); + + // now do the layout. + mViewRoot.layout(0, mScreenOffset, mMeasuredScreenWidth, mMeasuredScreenHeight); + + // draw the views + // create the BufferedImage into which the layout will be rendered. + if (newRenderSize || mCanvas == null) { + if (mParams.getImageFactory() != null) { + mImage = mParams.getImageFactory().getImage(mMeasuredScreenWidth, + mMeasuredScreenHeight - mScreenOffset); + } else { + mImage = new BufferedImage(mMeasuredScreenWidth, + mMeasuredScreenHeight - mScreenOffset, BufferedImage.TYPE_INT_ARGB); + } + + if (mParams.isBgColorOverridden()) { + Graphics2D gc = mImage.createGraphics(); + gc.setColor(new Color(mParams.getOverrideBgColor(), true)); + gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight - mScreenOffset); + gc.dispose(); + } + + // create an Android bitmap around the BufferedImage + Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage, + true /*isMutable*/, + ResourceDensity.getEnum(mParams.getDensity())); + + // create a Canvas around the Android bitmap + mCanvas = new Canvas(bitmap); + mCanvas.setDensity(mParams.getDensity()); + } + + mViewRoot.draw(mCanvas); + + mViewInfo = visit(((ViewGroup)mViewRoot).getChildAt(0), mContext); + + // success! + return SUCCESS.createResult(); + } catch (Throwable e) { + // get the real cause of the exception. + Throwable t = e; + while (t.getCause() != null) { + t = t.getCause(); + } + + // log it + mParams.getLog().error("Scene Render failed", t); + + return ERROR_UNKNOWN.createResult(t.getMessage(), t); + } + } + + /** + * Animate an object + *

+ * {@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 LayoutScene#animate(Object, String, boolean, IAnimationListener) + */ + public Result animate(Object targetObject, String animationName, + boolean isFrameworkAnimation, IAnimationListener listener) { + checkLock(); + + // find the animation file. + ResourceValue animationResource = null; + int animationId = 0; + if (isFrameworkAnimation) { + animationResource = mContext.getFrameworkResource("anim", animationName); + if (animationResource != null) { + animationId = Bridge.getResourceValue("anim", animationName); + } + } else { + animationResource = mContext.getProjectResource("anim", animationName); + if (animationResource != null) { + animationId = mContext.getProjectCallback().getResourceValue("anim", animationName); + } + } + + if (animationResource != null) { + try { + Animator anim = AnimatorInflater.loadAnimator(mContext, 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. + *

+ * {@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 LayoutScene#insertChild(Object, ILayoutPullParser, int, IAnimationListener) + */ + public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml, + final int index, IAnimationListener listener) { + checkLock(); + + // create a block parser for the XML + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(childXml, mContext, + 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*/); + + 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(); + 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 + *

+ * {@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 LayoutScene#moveChild(Object, Object, int, Map, IAnimationListener) + */ + public Result moveChild(final ViewGroup parentView, final View childView, final int index, + Map layoutParamsMap, IAnimationListener listener) { + checkLock(); + + invalidateRenderingSize(); + + LayoutParams layoutParams = null; + if (layoutParamsMap != null) { + // need to create a new LayoutParams object for the new parent. + layoutParams = parentView.generateLayoutParams( + new BridgeLayoutParamsMapAttributes(layoutParamsMap)); + } + + if (listener != null) { + final LayoutParams params = layoutParams; + new AnimationThread(this, "moveChild", listener) { + + @Override + public Result preAnimation() { + parentView.setLayoutTransition(new LayoutTransition()); + return moveView(parentView, childView, index, params); + } + + @Override + public void postAnimation() { + parentView.setLayoutTransition(null); + } + }.start(); + + // always return success since the real status will come through the listener. + return SUCCESS.createResult(layoutParams); + } + + Result result = moveView(parentView, childView, index, layoutParams); + if (result.isSuccess() == false) { + return result; + } + + result = render(); + 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 parent the new parent + * @param view 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 parent, View view, int index, LayoutParams params) { + try { + ViewGroup previousParent = (ViewGroup) view.getParent(); + previousParent.removeView(view); + + // add it to the parentView in the correct location + + if (params != null) { + parent.addView(view, index, params); + } else { + 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(); + } + } + + /** + * Removes a child from its current parent. + *

+ * {@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 LayoutScene#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(); + } + + /** + * 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(); + } + } + + /** + * 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. + */ + private 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"); + } + } + + + /** + * Compute style information from the given list of style for the project and framework. + * @param themeName the name of the current theme. In order to differentiate project and + * platform themes sharing the same name, all project themes must be prepended with + * a '*' character. + * @param isProjectTheme Is this a project theme + * @param inProjectStyleMap the project style map + * @param inFrameworkStyleMap the framework style map + * @param outInheritanceMap the map of style inheritance. This is filled by the method + * @return the {@link StyleResourceValue} matching themeName + */ + private StyleResourceValue computeStyleMaps( + String themeName, boolean isProjectTheme, Map inProjectStyleMap, Map inFrameworkStyleMap, + Map outInheritanceMap) { + + if (inProjectStyleMap != null && inFrameworkStyleMap != null) { + // first, get the theme + ResourceValue theme = null; + + // project theme names have been prepended with a * + if (isProjectTheme) { + theme = inProjectStyleMap.get(themeName); + } else { + theme = inFrameworkStyleMap.get(themeName); + } + + if (theme instanceof StyleResourceValue) { + // compute the inheritance map for both the project and framework styles + computeStyleInheritance(inProjectStyleMap.values(), inProjectStyleMap, + inFrameworkStyleMap, outInheritanceMap); + + // Compute the style inheritance for the framework styles/themes. + // Since, for those, the style parent values do not contain 'android:' + // we want to force looking in the framework style only to avoid using + // similarly named styles from the project. + // To do this, we pass null in lieu of the project style map. + computeStyleInheritance(inFrameworkStyleMap.values(), null /*inProjectStyleMap */, + inFrameworkStyleMap, outInheritanceMap); + + return (StyleResourceValue)theme; + } + } + + return null; + } + + /** + * Compute the parent style for all the styles in a given list. + * @param styles the styles for which we compute the parent. + * @param inProjectStyleMap the map of project styles. + * @param inFrameworkStyleMap the map of framework styles. + * @param outInheritanceMap the map of style inheritance. This is filled by the method. + */ + private void computeStyleInheritance(Collection styles, + Map inProjectStyleMap, + Map inFrameworkStyleMap, + Map outInheritanceMap) { + for (ResourceValue value : styles) { + if (value instanceof StyleResourceValue) { + StyleResourceValue style = (StyleResourceValue)value; + StyleResourceValue parentStyle = null; + + // first look for a specified parent. + String parentName = style.getParentStyle(); + + // no specified parent? try to infer it from the name of the style. + if (parentName == null) { + parentName = getParentName(value.getName()); + } + + if (parentName != null) { + parentStyle = getStyle(parentName, inProjectStyleMap, inFrameworkStyleMap); + + if (parentStyle != null) { + outInheritanceMap.put(style, parentStyle); + } + } + } + } + } + + /** + * Searches for and returns the {@link StyleResourceValue} from a given name. + *

The format of the name can be: + *

    + *
  • [android:]<name>
  • + *
  • [android:]style/<name>
  • + *
  • @[android:]style/<name>
  • + *
+ * @param parentName the name of the style. + * @param inProjectStyleMap the project style map. Can be null + * @param inFrameworkStyleMap the framework style map. + * @return The matching {@link StyleResourceValue} object or null if not found. + */ + private StyleResourceValue getStyle(String parentName, + Map inProjectStyleMap, + Map inFrameworkStyleMap) { + boolean frameworkOnly = false; + + String name = parentName; + + // remove the useless @ if it's there + if (name.startsWith(BridgeConstants.PREFIX_RESOURCE_REF)) { + name = name.substring(BridgeConstants.PREFIX_RESOURCE_REF.length()); + } + + // check for framework identifier. + if (name.startsWith(BridgeConstants.PREFIX_ANDROID)) { + frameworkOnly = true; + name = name.substring(BridgeConstants.PREFIX_ANDROID.length()); + } + + // at this point we could have the format /. we want only the name as long as + // the type is style. + if (name.startsWith(BridgeConstants.REFERENCE_STYLE)) { + name = name.substring(BridgeConstants.REFERENCE_STYLE.length()); + } else if (name.indexOf('/') != -1) { + return null; + } + + ResourceValue parent = null; + + // if allowed, search in the project resources. + if (frameworkOnly == false && inProjectStyleMap != null) { + parent = inProjectStyleMap.get(name); + } + + // if not found, then look in the framework resources. + if (parent == null) { + parent = inFrameworkStyleMap.get(name); + } + + // make sure the result is the proper class type and return it. + if (parent instanceof StyleResourceValue) { + return (StyleResourceValue)parent; + } + + assert false; + mParams.getLog().error(null, + String.format("Unable to resolve parent style name: %s", parentName)); + + return null; + } + + /** + * Computes the name of the parent style, or null if the style is a root style. + */ + private String getParentName(String styleName) { + int index = styleName.lastIndexOf('.'); + if (index != -1) { + return styleName.substring(0, index); + } + + return null; + } + + /** + * Returns the top screen offset. This depends on whether the current theme defines the user + * of the title and status bars. + * @param frameworkResources The framework resources + * @param currentTheme The current theme + * @param context The context + * @return the pixel height offset + */ + private int getScreenOffset(Map> frameworkResources, + StyleResourceValue currentTheme, BridgeContext context) { + int offset = 0; + + // get the title bar flag from the current theme. + ResourceValue value = context.findItemInStyle(currentTheme, "windowNoTitle"); + + // because it may reference something else, we resolve it. + value = context.resolveResValue(value); + + // if there's a value and it's true (default is false) + if (value == null || value.getValue() == null || + XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { + // default size of the window title bar + int defaultOffset = DEFAULT_TITLE_BAR_HEIGHT; + + // get value from the theme. + value = context.findItemInStyle(currentTheme, "windowTitleSize"); + + // resolve it + value = context.resolveResValue(value); + + if (value != null) { + // get the numerical value, if available + TypedValue typedValue = ResourceHelper.getValue(value.getValue()); + if (typedValue != null) { + // compute the pixel value based on the display metrics + defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics); + } + } + + offset += defaultOffset; + } + + // get the fullscreen flag from the current theme. + value = context.findItemInStyle(currentTheme, "windowFullscreen"); + + // because it may reference something else, we resolve it. + value = context.resolveResValue(value); + + if (value == null || value.getValue() == null || + XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { + + // default value + int defaultOffset = DEFAULT_STATUS_BAR_HEIGHT; + + // get the real value, first the list of Dimensions from the framework map + Map dimens = frameworkResources.get(BridgeConstants.RES_DIMEN); + + // now get the value + value = dimens.get("status_bar_height"); + if (value != null) { + TypedValue typedValue = ResourceHelper.getValue(value.getValue()); + if (typedValue != null) { + // compute the pixel value based on the display metrics + defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics); + } + } + + // add the computed offset. + offset += defaultOffset; + } + + return offset; + + } + + /** + * Post process on a view hierachy that was just inflated. + *

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 ViewGroup) { + ViewGroup group = (ViewGroup)view; + final int count = group.getChildCount(); + for (int c = 0 ; c < count ; c++) { + View child = group.getChildAt(c); + postInflateProcess(child, projectCallback); + } + } + } + + /** + * Sets up a {@link TabHost} object. + * @param tabHost the TabHost to setup. + * @param projectCallback The project callback object to access the project R class. + * @throws PostInflateException + */ + private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback) + throws PostInflateException { + // look for the TabWidget, and the FrameLayout. They have their own specific names + View v = tabHost.findViewById(android.R.id.tabs); + + if (v == null) { + throw new PostInflateException( + "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); + } + + if ((v instanceof TabWidget) == false) { + throw new PostInflateException(String.format( + "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + + "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); + } + + v = tabHost.findViewById(android.R.id.tabcontent); + + if (v == null) { + // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) + throw new PostInflateException( + "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); + } + + if ((v instanceof FrameLayout) == false) { + throw new PostInflateException(String.format( + "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + + "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); + } + + FrameLayout content = (FrameLayout)v; + + // now process the content of the framelayout and dynamically create tabs for it. + final int count = content.getChildCount(); + + if (count == 0) { + throw new PostInflateException( + "The FrameLayout for the TabHost has no content. Rendering failed.\n"); + } + + // this must be called before addTab() so that the TabHost searches its TabWidget + // and FrameLayout. + tabHost.setup(); + + // for each child of the framelayout, add a new TabSpec + for (int i = 0 ; i < count ; i++) { + View child = content.getChildAt(i); + String tabSpec = String.format("tab_spec%d", i+1); + int id = child.getId(); + String[] resource = projectCallback.resolveResourceValue(id); + String name; + if (resource != null) { + name = resource[0]; // 0 is resource name, 1 is resource type. + } else { + name = String.format("Tab %d", i+1); // default name if id is unresolved. + } + tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id)); + } + } + + + /** + * Visits a View and its children and generate a {@link ViewInfo} containing the + * bounds of all the views. + * @param view the root View + * @param context the context. + */ + private ViewInfo visit(View view, BridgeContext context) { + if (view == null) { + return null; + } + + ViewInfo result = new ViewInfo(view.getClass().getName(), + context.getViewKey(view), + view.getLeft(), view.getTop(), view.getRight(), view.getBottom(), + view, view.getLayoutParams()); + + if (view instanceof ViewGroup) { + ViewGroup group = ((ViewGroup) view); + List children = new ArrayList(); + for (int i = 0; i < group.getChildCount(); i++) { + children.add(visit(group.getChildAt(i), context)); + } + result.setChildren(children); + } + + return result; + } + + private void invalidateRenderingSize() { + mMeasuredScreenWidth = mMeasuredScreenHeight = -1; + } + + public BufferedImage getImage() { + return mImage; + } + + public ViewInfo getViewInfo() { + return mViewInfo; + } + + public Map getDefaultProperties(Object viewObject) { + return mContext.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 index f03931f..5427f142 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -16,9 +16,9 @@ package com.android.layoutlib.bridge.impl; -import com.android.layoutlib.api.DensityBasedResourceValue; -import com.android.layoutlib.api.ResourceDensity; -import com.android.layoutlib.api.ResourceValue; +import com.android.ide.common.rendering.api.DensityBasedResourceValue; +import com.android.ide.common.rendering.api.ResourceDensity; +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; -- cgit v1.1