summaryrefslogtreecommitdiffstats
path: root/tools/layoutlib
diff options
context:
space:
mode:
Diffstat (limited to 'tools/layoutlib')
-rw-r--r--tools/layoutlib/.idea/codeStyleSettings.xml8
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow-b.pngbin0 -> 215 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow-bl.pngbin0 -> 397 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow-br.pngbin0 -> 406 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow-l.pngbin0 -> 120 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow-r.pngbin0 -> 207 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow-tl.pngbin0 -> 277 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow-tr.pngbin0 -> 397 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow2-b.pngbin0 -> 195 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow2-bl.pngbin0 -> 277 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow2-br.pngbin0 -> 282 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow2-l.pngbin0 -> 108 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow2-r.pngbin0 -> 192 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow2-tl.pngbin0 -> 2855 bytes
-rw-r--r--tools/layoutlib/bridge/resources/icons/shadow2-tr.pngbin0 -> 286 bytes
-rw-r--r--tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java59
-rw-r--r--tools/layoutlib/bridge/src/android/animation/FakeAnimator.java52
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java4
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java2
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeResources.java2
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java10
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java48
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java7
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java6
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java23
-rw-r--r--tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java52
-rw-r--r--tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java83
-rw-r--r--tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java4
-rw-r--r--tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java5
-rw-r--r--tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java75
-rw-r--r--tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java5
-rw-r--r--tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java72
-rw-r--r--tools/layoutlib/bridge/src/android/view/ShadowPainter.java415
-rw-r--r--tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java205
-rw-r--r--tools/layoutlib/bridge/src/android/view/WindowCallback.java131
-rw-r--r--tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java44
-rw-r--r--tools/layoutlib/bridge/src/android/widget/Toolbar_Accessor.java32
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java51
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java2
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java119
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java10
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeSharedPreferences.java138
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java8
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/SessionParamsFlags.java35
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java330
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java380
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java86
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java81
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java14
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java76
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java5
-rw-r--r--tools/layoutlib/bridge/src/libcore/io/MemoryMappedFile_Delegate.java118
-rw-r--r--tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java (renamed from tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java)17
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.pngbin0 -> 3272 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml6
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java336
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java32
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java22
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java1
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java1
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java119
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/java/LinkedHashMap_Delegate.java36
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java34
64 files changed, 2882 insertions, 523 deletions
diff --git a/tools/layoutlib/.idea/codeStyleSettings.xml b/tools/layoutlib/.idea/codeStyleSettings.xml
index b324213..a04e440 100644
--- a/tools/layoutlib/.idea/codeStyleSettings.xml
+++ b/tools/layoutlib/.idea/codeStyleSettings.xml
@@ -67,9 +67,13 @@
</groups>
</arrangement>
</codeStyleSettings>
+ <codeStyleSettings language="XML">
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="4" />
+ </indentOptions>
+ </codeStyleSettings>
</value>
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</component>
-</project>
-
+</project> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-b.png b/tools/layoutlib/bridge/resources/icons/shadow-b.png
new file mode 100644
index 0000000..68f4f4b
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-b.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-bl.png b/tools/layoutlib/bridge/resources/icons/shadow-bl.png
new file mode 100644
index 0000000..ee7dbe8
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-bl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-br.png b/tools/layoutlib/bridge/resources/icons/shadow-br.png
new file mode 100644
index 0000000..c45ad77
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-br.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-l.png b/tools/layoutlib/bridge/resources/icons/shadow-l.png
new file mode 100644
index 0000000..77d0bd0
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-l.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-r.png b/tools/layoutlib/bridge/resources/icons/shadow-r.png
new file mode 100644
index 0000000..4af7a33
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-r.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-tl.png b/tools/layoutlib/bridge/resources/icons/shadow-tl.png
new file mode 100644
index 0000000..424fb36
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-tl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-tr.png b/tools/layoutlib/bridge/resources/icons/shadow-tr.png
new file mode 100644
index 0000000..1fd0c77
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-tr.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-b.png b/tools/layoutlib/bridge/resources/icons/shadow2-b.png
new file mode 100644
index 0000000..963973e
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-b.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-bl.png b/tools/layoutlib/bridge/resources/icons/shadow2-bl.png
new file mode 100644
index 0000000..7612487
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-bl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-br.png b/tools/layoutlib/bridge/resources/icons/shadow2-br.png
new file mode 100644
index 0000000..8e20252
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-br.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-l.png b/tools/layoutlib/bridge/resources/icons/shadow2-l.png
new file mode 100644
index 0000000..2db18a0
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-l.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-r.png b/tools/layoutlib/bridge/resources/icons/shadow2-r.png
new file mode 100644
index 0000000..8e026f1
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-r.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-tl.png b/tools/layoutlib/bridge/resources/icons/shadow2-tl.png
new file mode 100644
index 0000000..a8045ed
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-tl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-tr.png b/tools/layoutlib/bridge/resources/icons/shadow2-tr.png
new file mode 100644
index 0000000..590373c
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-tr.png
Binary files differ
diff --git a/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java b/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java
new file mode 100644
index 0000000..4475fa4
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.animation;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.util.AttributeSet;
+
+/**
+ * Delegate providing alternate implementation to static methods in {@link AnimatorInflater}.
+ */
+public class AnimatorInflater_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static Animator loadAnimator(Context context, int id)
+ throws NotFoundException {
+ return loadAnimator(context.getResources(), context.getTheme(), id);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id)
+ throws NotFoundException {
+ return loadAnimator(resources, theme, id, 1);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id,
+ float pathErrorScale) throws NotFoundException {
+ // This is a temporary fix to http://b.android.com/77865. This skips loading the
+ // animation altogether.
+ // TODO: Remove this override when Path.approximate() is supported.
+ return new FakeAnimator();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static ValueAnimator loadAnimator(Resources res, Theme theme,
+ AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
+ throws NotFoundException {
+ return AnimatorInflater.loadAnimator_Original(res, theme, attrs, anim, pathErrorScale);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java b/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java
new file mode 100644
index 0000000..78aedc5
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.animation;
+
+/**
+ * A fake implementation of Animator which doesn't do anything.
+ */
+public class FakeAnimator extends Animator {
+ @Override
+ public long getStartDelay() {
+ return 0;
+ }
+
+ @Override
+ public void setStartDelay(long startDelay) {
+
+ }
+
+ @Override
+ public Animator setDuration(long duration) {
+ return this;
+ }
+
+ @Override
+ public long getDuration() {
+ return 0;
+ }
+
+ @Override
+ public void setInterpolator(TimeInterpolator value) {
+
+ }
+
+ @Override
+ public boolean isRunning() {
+ return false;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java
index 914a359..e0d3b8c 100644
--- a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java
@@ -38,8 +38,4 @@ public class AssetManager_Delegate {
Resources_Theme_Delegate.getDelegateManager().removeJavaReferenceFor(theme);
}
- @LayoutlibDelegate
- /*package*/ static void applyThemeStyle(long theme, int styleRes, boolean force) {
- Resources_Theme_Delegate.getDelegateManager().getDelegate(theme).force = force;
- }
}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
index 93814b2..c41a4ee 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
@@ -23,7 +23,7 @@ import android.content.res.AssetManager;
public class BridgeAssetManager extends AssetManager {
/**
- * This initializes the static field {@link AssetManager#mSystem} which is used
+ * This initializes the static field {@link AssetManager#sSystem} which is used
* by methods who get a global asset manager using {@link AssetManager#getSystem()}.
* <p/>
* They will end up using our bridge asset manager.
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
index dd573be..66126af 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
@@ -163,7 +163,7 @@ public final class BridgeResources extends Resources {
Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
if (value != null) {
- return ResourceHelper.getDrawable(value.getSecond(), mContext);
+ return ResourceHelper.getDrawable(value.getSecond(), mContext, theme);
}
// id was not found or not resolved. Throw a NotFoundException.
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 28a109d..a2bd6d7 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -32,6 +32,7 @@ import com.android.resources.ResourceType;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@@ -116,6 +117,13 @@ public final class BridgeTypedArray extends TypedArray {
}
/**
+ * Set the theme to be used for inflating drawables.
+ */
+ public void setTheme(Theme theme) {
+ mTheme = theme;
+ }
+
+ /**
* Return the number of values in this array.
*/
@Override
@@ -663,7 +671,7 @@ public final class BridgeTypedArray extends TypedArray {
}
ResourceValue value = mResourceData[index];
- return ResourceHelper.getDrawable(value, mContext);
+ return ResourceHelper.getDrawable(value, mContext, mTheme);
}
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java
index f4a9f52..4bd83e9 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java
@@ -39,9 +39,6 @@ import android.util.TypedValue;
*/
public class Resources_Theme_Delegate {
- // Whether to use the Theme.mThemeResId as primary theme.
- boolean force;
-
// ---- delegate manager ----
private static final DelegateManager<Resources_Theme_Delegate> sManager =
@@ -58,7 +55,8 @@ public class Resources_Theme_Delegate {
Resources thisResources, Theme thisTheme,
int[] attrs) {
boolean changed = setupResources(thisTheme);
- TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs);
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs);
+ ta.setTheme(thisTheme);
restoreResources(changed);
return ta;
}
@@ -69,7 +67,9 @@ public class Resources_Theme_Delegate {
int resid, int[] attrs)
throws NotFoundException {
boolean changed = setupResources(thisTheme);
- TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs);
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid,
+ attrs);
+ ta.setTheme(thisTheme);
restoreResources(changed);
return ta;
}
@@ -79,8 +79,9 @@ public class Resources_Theme_Delegate {
Resources thisResources, Theme thisTheme,
AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
boolean changed = setupResources(thisTheme);
- TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(set, attrs,
- defStyleAttr, defStyleRes);
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(set,
+ attrs, defStyleAttr, defStyleRes);
+ ta.setTheme(thisTheme);
restoreResources(changed);
return ta;
}
@@ -91,8 +92,8 @@ public class Resources_Theme_Delegate {
int resid, TypedValue outValue,
boolean resolveRefs) {
boolean changed = setupResources(thisTheme);
- boolean found = RenderSessionImpl.getCurrentContext().resolveThemeAttribute(
- resid, outValue, resolveRefs);
+ boolean found = RenderSessionImpl.getCurrentContext().resolveThemeAttribute(resid,
+ outValue, resolveRefs);
restoreResources(changed);
return found;
}
@@ -107,14 +108,29 @@ public class Resources_Theme_Delegate {
// ---- private helper methods ----
private static boolean setupResources(Theme thisTheme) {
- Resources_Theme_Delegate themeDelegate = sManager.getDelegate(thisTheme.getNativeTheme());
- StyleResourceValue style = resolveStyle(thisTheme.getAppliedStyleResId());
- if (style != null) {
- RenderSessionImpl.getCurrentContext().getRenderResources()
- .applyStyle(style, themeDelegate.force);
- return true;
+ // Key is a space-separated list of theme ids applied that have been merged into the
+ // BridgeContext's theme to make thisTheme.
+ String[] appliedStyles = thisTheme.getKey().split(" ");
+ boolean changed = false;
+ for (String s : appliedStyles) {
+ if (s.isEmpty()) {
+ continue;
+ }
+ // See the definition of force parameter in Theme.applyStyle().
+ boolean force = false;
+ if (s.charAt(s.length() - 1) == '!') {
+ force = true;
+ s = s.substring(0, s.length() - 1);
+ }
+ int styleId = Integer.parseInt(s, 16);
+ StyleResourceValue style = resolveStyle(styleId);
+ if (style != null) {
+ RenderSessionImpl.getCurrentContext().getRenderResources().applyStyle(style, force);
+ changed = true;
+ }
+
}
- return false;
+ return changed;
}
private static void restoreResources(boolean changed) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index f4282ad..8d24d38 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -141,7 +141,6 @@ public final class Bitmap_Delegate {
* Creates and returns a {@link Bitmap} initialized with the given stream content.
*
* @param input the stream from which to read the bitmap content
- * @param createFlags
* @param density the density associated with the bitmap
*
* @see Bitmap#isPremultiplied()
@@ -166,8 +165,7 @@ public final class Bitmap_Delegate {
* @see Bitmap#isMutable()
* @see Bitmap#getDensity()
*/
- public static Bitmap createBitmap(BufferedImage image, boolean isMutable,
- Density density) throws IOException {
+ public static Bitmap createBitmap(BufferedImage image, boolean isMutable, Density density) {
return createBitmap(image, getPremultipliedBitmapCreateFlags(isMutable), density);
}
@@ -175,7 +173,6 @@ public final class Bitmap_Delegate {
* Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
*
* @param image the bitmap content
- * @param createFlags
* @param density the density associated with the bitmap
*
* @see Bitmap#isPremultiplied()
@@ -183,7 +180,7 @@ public final class Bitmap_Delegate {
* @see Bitmap#getDensity()
*/
public static Bitmap createBitmap(BufferedImage image, Set<BitmapCreateFlags> createFlags,
- Density density) throws IOException {
+ Density density) {
// create a delegate with the given image.
Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index bef5181..ab79664 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -213,6 +213,10 @@ public class FontFamily_Delegate {
return null;
}
+ @Nullable
+ /*package*/ static String getFontLocation() {
+ return sFontLocation;
+ }
// ---- native methods ----
@@ -278,7 +282,7 @@ public class FontFamily_Delegate {
@LayoutlibDelegate
/*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "FontFamily.addFontFromAsset is not supported.", null, null);
+ "Typeface.createFromAsset is not supported.", null, null);
return false;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 276e134..b9460b4 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -27,6 +27,8 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
+import static android.graphics.FontFamily_Delegate.getFontLocation;
+
/**
* Delegate implementing the native methods of android.graphics.Typeface
*
@@ -48,8 +50,6 @@ public final class Typeface_Delegate {
private static final DelegateManager<Typeface_Delegate> sManager =
new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class);
- // ---- delegate helper data ----
- private static String sFontLocation;
// ---- delegate data ----
@@ -61,11 +61,8 @@ public final class Typeface_Delegate {
private static long sDefaultTypeface;
+
// ---- Public Helper methods ----
- public static synchronized void setFontLocation(String fontLocation) {
- sFontLocation = fontLocation;
- FontFamily_Delegate.setFontLocation(fontLocation);
- }
public static Typeface_Delegate getDelegate(long nativeTypeface) {
return sManager.getDelegate(nativeTypeface);
@@ -131,6 +128,18 @@ public final class Typeface_Delegate {
return fonts;
}
+ /**
+ * Clear the default typefaces when disposing bridge.
+ */
+ public static void resetDefaults() {
+ // Sometimes this is called before the Bridge is initialized. In that case, we don't want to
+ // initialize Typeface because the SDK fonts location hasn't been set.
+ if (FontFamily_Delegate.getFontLocation() != null) {
+ Typeface.sDefaults = null;
+ }
+ }
+
+
// ---- native methods ----
@LayoutlibDelegate
@@ -193,7 +202,7 @@ public final class Typeface_Delegate {
@LayoutlibDelegate
/*package*/ static File getSystemFontConfigLocation() {
- return new File(sFontLocation);
+ return new File(getFontLocation());
}
// ---- Private delegate/helper methods ----
diff --git a/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java b/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java
new file mode 100644
index 0000000..4f00b5d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.preference;
+
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+public class BridgePreferenceInflater extends PreferenceInflater {
+
+ public BridgePreferenceInflater(Context context, PreferenceManager preferenceManager) {
+ super(context, preferenceManager);
+ }
+
+ @Override
+ protected Preference onCreateItem(String name, AttributeSet attrs)
+ throws ClassNotFoundException {
+ Object viewKey = null;
+ BridgeContext bc = null;
+
+ Context context = getContext();
+ if (context instanceof BridgeContext) {
+ bc = (BridgeContext) context;
+ }
+ if (attrs instanceof BridgeXmlBlockParser) {
+ viewKey = ((BridgeXmlBlockParser) attrs).getViewCookie();
+ }
+
+ Preference preference = super.onCreateItem(name, attrs);
+
+ if (viewKey != null && bc != null) {
+ bc.addCookie(preference, viewKey);
+ }
+ return preference;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
new file mode 100644
index 0000000..49ee642
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.preference;
+
+import com.android.internal.R;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Delegate that provides implementation for native methods in {@link Preference}
+ * <p/>
+ * Through the layoutlib_create tool, selected methods of Preference have been replaced by calls to
+ * methods of the same name in this delegate class.
+ */
+public class Preference_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static View getView(Preference pref, View convertView, ViewGroup parent) {
+ Context context = pref.getContext();
+ BridgeContext bc = context instanceof BridgeContext ? ((BridgeContext) context) : null;
+ convertView = pref.getView_Original(convertView, parent);
+ if (bc != null) {
+ Object cookie = bc.getCookie(pref);
+ if (cookie != null) {
+ bc.addViewKey(convertView, cookie);
+ }
+ }
+ return convertView;
+ }
+
+ /**
+ * Inflates the parser and returns the ListView containing the Preferences.
+ */
+ public static View inflatePreference(Context context, XmlPullParser parser, ViewGroup root) {
+ PreferenceManager pm = new PreferenceManager(context);
+ PreferenceScreen ps = pm.getPreferenceScreen();
+ PreferenceInflater inflater = new BridgePreferenceInflater(context, pm);
+ ps = (PreferenceScreen) inflater.inflate(parser, ps, true);
+ ListView preferenceView = createContainerView(context, root);
+ ps.bind(preferenceView);
+ return preferenceView;
+ }
+
+ private static ListView createContainerView(Context context, ViewGroup root) {
+ TypedArray a = context.obtainStyledAttributes(null, R.styleable.PreferenceFragment,
+ R.attr.preferenceFragmentStyle, 0);
+ int mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_layout,
+ R.layout.preference_list_fragment);
+ a.recycle();
+
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(mLayoutResId, root, true);
+
+ return (ListView) root.findViewById(android.R.id.list);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
index 5a467b2..b0d79a8 100644
--- a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
@@ -13,8 +13,8 @@ import javax.swing.text.Segment;
/**
* Delegate that provides implementation for native methods in {@link android.text.StaticLayout}
- *
- * Through the layoutlib_create tool, selected methods of Handler have been replaced
+ * <p/>
+ * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
* by calls to methods of the same name in this delegate class.
*
*/
diff --git a/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java
index 8cd1a69..1e4f213 100644
--- a/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java
@@ -34,4 +34,9 @@ public class DateFormat_Delegate {
/*package*/ static boolean is24HourFormat(Context context) {
return false;
}
+
+ @LayoutlibDelegate
+ /*package*/ static boolean is24HourFormat(Context context, int userHandle) {
+ return false;
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java
deleted file mode 100644
index ed8498f..0000000
--- a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text.format;
-
-import java.util.Calendar;
-import java.util.TimeZone;
-import java.util.UnknownFormatConversionException;
-import java.util.regex.Pattern;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate used to provide new implementation for native methods of {@link Time}
- *
- * Through the layoutlib_create tool, some native methods of Time have been replaced by calls to
- * methods of the same name in this delegate class.
- */
-public class Time_Delegate {
-
- // Regex to match odd number of '%'.
- private static final Pattern p = Pattern.compile("(?<!%)(%%)*%(?!%)");
-
- // Format used by toString()
- private static final String FORMAT = "%1$tY%1$tm%1$tdT%1$tH%1$tM%1$tS<%1$tZ>";
-
- // ---- private helper methods ----
-
- private static Calendar timeToCalendar(Time time) {
- Calendar calendar = getCalendarInstance(time);
- calendar.set(time.year, time.month, time.monthDay, time.hour, time.minute, time.second);
- return calendar;
- }
-
- private static void calendarToTime(Calendar c, Time time) {
- time.timezone = c.getTimeZone().getID();
- time.set(c.get(Calendar.SECOND), c.get(Calendar.MINUTE), c.get(Calendar.HOUR_OF_DAY),
- c.get(Calendar.DATE), c.get(Calendar.MONTH), c.get(Calendar.YEAR));
- time.weekDay = c.get(Calendar.DAY_OF_WEEK);
- time.yearDay = c.get(Calendar.DAY_OF_YEAR);
- time.isDst = c.getTimeZone().inDaylightTime(c.getTime()) ? 1 : 0;
- // gmtoff is in seconds and TimeZone.getOffset() returns milliseconds.
- time.gmtoff = c.getTimeZone().getOffset(c.getTimeInMillis()) / DateUtils.SECOND_IN_MILLIS;
- }
-
- /**
- * Return a calendar instance with the correct timezone.
- *
- * @param time Time to obtain the timezone from.
- */
- private static Calendar getCalendarInstance(Time time) {
- // TODO: Check platform code to make sure the behavior is same for null/invalid timezone.
- if (time == null || time.timezone == null) {
- // Default to local timezone.
- return Calendar.getInstance();
- }
- // If timezone is invalid, use GMT.
- return Calendar.getInstance(TimeZone.getTimeZone(time.timezone));
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index c403ce6..5176419 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -228,6 +228,11 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
+ public void overridePendingAppTransitionInPlace(String packageName, int anim) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
public void pauseKeyDispatching(IBinder arg0) throws RemoteException {
// TODO Auto-generated method stub
diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
new file mode 100644
index 0000000..6c949d9
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of {@link RenderNode}
+ * <p/>
+ * Through the layoutlib_create tool, some native methods of RenderNode have been replaced by calls
+ * to methods of the same name in this delegate class.
+ *
+ * @see DelegateManager
+ */
+public class RenderNode_Delegate {
+
+
+ // ---- delegate manager ----
+ private static final DelegateManager<RenderNode_Delegate> sManager =
+ new DelegateManager<RenderNode_Delegate>(RenderNode_Delegate.class);
+
+
+ private float mLift;
+ @SuppressWarnings("UnusedDeclaration")
+ private String mName;
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreate(String name) {
+ RenderNode_Delegate renderNodeDelegate = new RenderNode_Delegate();
+ renderNodeDelegate.mName = name;
+ return sManager.addNewDelegate(renderNodeDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDestroyRenderNode(long renderNode) {
+ sManager.removeJavaReferenceFor(renderNode);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetElevation(long renderNode, float lift) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mLift != lift) {
+ delegate.mLift = lift;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetElevation(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mLift;
+ }
+ return 0f;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
new file mode 100644
index 0000000..38846bd
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.annotations.NonNull;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.ImageIO;
+
+public class ShadowPainter {
+
+ /**
+ * Adds a drop shadow to a semi-transparent image (of an arbitrary shape) and returns it as a
+ * new image. This method attempts to mimic the same visual characteristics as the rectangular
+ * shadow painting methods in this class, {@link #createRectangularDropShadow(java.awt.image.BufferedImage)}
+ * and {@link #createSmallRectangularDropShadow(java.awt.image.BufferedImage)}.
+ *
+ * @param source the source image
+ * @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
+ * #SMALL_SHADOW_SIZE}}
+ *
+ * @return a new image with the shadow painted in
+ */
+ @NonNull
+ public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) {
+ shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class
+
+ return createDropShadow(source, shadowSize, 0.7f, 0);
+ }
+
+ /**
+ * Creates a drop shadow of a given image and returns a new image which shows the input image on
+ * top of its drop shadow.
+ * <p/>
+ * <b>NOTE: If the shape is rectangular and opaque, consider using {@link
+ * #drawRectangleShadow(Graphics2D, int, int, int, int)} instead.</b>
+ *
+ * @param source the source image to be shadowed
+ * @param shadowSize the size of the shadow in pixels
+ * @param shadowOpacity the opacity of the shadow, with 0=transparent and 1=opaque
+ * @param shadowRgb the RGB int to use for the shadow color
+ *
+ * @return a new image with the source image on top of its shadow
+ */
+ @SuppressWarnings({"SuspiciousNameCombination", "UnnecessaryLocalVariable"}) // Imported code
+ public static BufferedImage createDropShadow(BufferedImage source, int shadowSize,
+ float shadowOpacity, int shadowRgb) {
+
+ // This code is based on
+ // http://www.jroller.com/gfx/entry/non_rectangular_shadow
+
+ BufferedImage image;
+ int width = source.getWidth();
+ int height = source.getHeight();
+ image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE,
+ BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g2 = image.createGraphics();
+ g2.drawImage(image, shadowSize, shadowSize, null);
+
+ int dstWidth = image.getWidth();
+ int dstHeight = image.getHeight();
+
+ int left = (shadowSize - 1) >> 1;
+ int right = shadowSize - left;
+ int xStart = left;
+ int xStop = dstWidth - right;
+ int yStart = left;
+ int yStop = dstHeight - right;
+
+ shadowRgb &= 0x00FFFFFF;
+
+ int[] aHistory = new int[shadowSize];
+ int historyIdx;
+
+ int aSum;
+
+ int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
+ int lastPixelOffset = right * dstWidth;
+ float sumDivider = shadowOpacity / shadowSize;
+
+ // horizontal pass
+ for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) {
+ aSum = 0;
+ historyIdx = 0;
+ for (int x = 0; x < shadowSize; x++, bufferOffset++) {
+ int a = dataBuffer[bufferOffset] >>> 24;
+ aHistory[x] = a;
+ aSum += a;
+ }
+
+ bufferOffset -= right;
+
+ for (int x = xStart; x < xStop; x++, bufferOffset++) {
+ int a = (int) (aSum * sumDivider);
+ dataBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+ // subtract the oldest pixel from the sum
+ aSum -= aHistory[historyIdx];
+
+ // get the latest pixel
+ a = dataBuffer[bufferOffset + right] >>> 24;
+ aHistory[historyIdx] = a;
+ aSum += a;
+
+ if (++historyIdx >= shadowSize) {
+ historyIdx -= shadowSize;
+ }
+ }
+ }
+ // vertical pass
+ for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
+ aSum = 0;
+ historyIdx = 0;
+ for (int y = 0; y < shadowSize; y++, bufferOffset += dstWidth) {
+ int a = dataBuffer[bufferOffset] >>> 24;
+ aHistory[y] = a;
+ aSum += a;
+ }
+
+ bufferOffset -= lastPixelOffset;
+
+ for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
+ int a = (int) (aSum * sumDivider);
+ dataBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+ // subtract the oldest pixel from the sum
+ aSum -= aHistory[historyIdx];
+
+ // get the latest pixel
+ a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
+ aHistory[historyIdx] = a;
+ aSum += a;
+
+ if (++historyIdx >= shadowSize) {
+ historyIdx -= shadowSize;
+ }
+ }
+ }
+
+ g2.drawImage(source, null, 0, 0);
+ g2.dispose();
+
+ return image;
+ }
+
+ /**
+ * Draws a rectangular drop shadow (of size {@link #SHADOW_SIZE} by {@link #SHADOW_SIZE} around
+ * the given source and returns a new image with both combined
+ *
+ * @param source the source image
+ *
+ * @return the source image with a drop shadow on the bottom and right
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ public static BufferedImage createRectangularDropShadow(BufferedImage source) {
+ int type = source.getType();
+ if (type == BufferedImage.TYPE_CUSTOM) {
+ type = BufferedImage.TYPE_INT_ARGB;
+ }
+
+ int width = source.getWidth();
+ int height = source.getHeight();
+ BufferedImage image;
+ image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE, type);
+ Graphics2D g = image.createGraphics();
+ g.drawImage(source, 0, 0, null);
+ drawRectangleShadow(image, 0, 0, width, height);
+ g.dispose();
+
+ return image;
+ }
+
+ /**
+ * Draws a small rectangular drop shadow (of size {@link #SMALL_SHADOW_SIZE} by {@link
+ * #SMALL_SHADOW_SIZE} around the given source and returns a new image with both combined
+ *
+ * @param source the source image
+ *
+ * @return the source image with a drop shadow on the bottom and right
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ public static BufferedImage createSmallRectangularDropShadow(BufferedImage source) {
+ int type = source.getType();
+ if (type == BufferedImage.TYPE_CUSTOM) {
+ type = BufferedImage.TYPE_INT_ARGB;
+ }
+
+ int width = source.getWidth();
+ int height = source.getHeight();
+
+ BufferedImage image;
+ image = new BufferedImage(width + SMALL_SHADOW_SIZE, height + SMALL_SHADOW_SIZE, type);
+
+ Graphics2D g = image.createGraphics();
+ g.drawImage(source, 0, 0, null);
+ drawSmallRectangleShadow(image, 0, 0, width, height);
+ g.dispose();
+
+ return image;
+ }
+
+ /**
+ * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
+ * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
+ * graphics. The size of the shadow is {@link #SHADOW_SIZE}.
+ *
+ * @param image the image to draw the shadow into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static void drawRectangleShadow(BufferedImage image,
+ int x, int y, int width, int height) {
+ Graphics2D gc = image.createGraphics();
+ try {
+ drawRectangleShadow(gc, x, y, width, height);
+ } finally {
+ gc.dispose();
+ }
+ }
+
+ /**
+ * Draws a small drop shadow for the given rectangle into the given context. It will not draw
+ * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
+ * shadow graphics. The size of the shadow is {@link #SMALL_SHADOW_SIZE}.
+ *
+ * @param image the image to draw the shadow into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static void drawSmallRectangleShadow(BufferedImage image,
+ int x, int y, int width, int height) {
+ Graphics2D gc = image.createGraphics();
+ try {
+ drawSmallRectangleShadow(gc, x, y, width, height);
+ } finally {
+ gc.dispose();
+ }
+ }
+
+ /**
+ * The width and height of the drop shadow painted by
+ * {@link #drawRectangleShadow(Graphics2D, int, int, int, int)}
+ */
+ public static final int SHADOW_SIZE = 20; // DO NOT EDIT. This corresponds to bitmap graphics
+
+ /**
+ * The width and height of the drop shadow painted by
+ * {@link #drawSmallRectangleShadow(Graphics2D, int, int, int, int)}
+ */
+ public static final int SMALL_SHADOW_SIZE = 10; // DO NOT EDIT. Corresponds to bitmap graphics
+
+ /**
+ * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
+ * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
+ * graphics.
+ *
+ * @param gc the graphics context to draw into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static void drawRectangleShadow(Graphics2D gc, int x, int y, int width, int height) {
+ assert ShadowBottomLeft != null;
+ assert ShadowBottomRight.getWidth(null) == SHADOW_SIZE;
+ assert ShadowBottomRight.getHeight(null) == SHADOW_SIZE;
+
+ int blWidth = ShadowBottomLeft.getWidth(null);
+ int trHeight = ShadowTopRight.getHeight(null);
+ if (width < blWidth) {
+ return;
+ }
+ if (height < trHeight) {
+ return;
+ }
+
+ gc.drawImage(ShadowBottomLeft, x - ShadowBottomLeft.getWidth(null), y + height, null);
+ gc.drawImage(ShadowBottomRight, x + width, y + height, null);
+ gc.drawImage(ShadowTopRight, x + width, y, null);
+ gc.drawImage(ShadowTopLeft, x - ShadowTopLeft.getWidth(null), y, null);
+ gc.drawImage(ShadowBottom,
+ x, y + height, x + width, y + height + ShadowBottom.getHeight(null),
+ 0, 0, ShadowBottom.getWidth(null), ShadowBottom.getHeight(null), null);
+ gc.drawImage(ShadowRight,
+ x + width, y + ShadowTopRight.getHeight(null), x + width + ShadowRight.getWidth(null), y + height,
+ 0, 0, ShadowRight.getWidth(null), ShadowRight.getHeight(null), null);
+ gc.drawImage(ShadowLeft,
+ x - ShadowLeft.getWidth(null), y + ShadowTopLeft.getHeight(null), x, y + height,
+ 0, 0, ShadowLeft.getWidth(null), ShadowLeft.getHeight(null), null);
+ }
+
+ /**
+ * Draws a small drop shadow for the given rectangle into the given context. It will not draw
+ * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
+ * shadow graphics.
+ * <p/>
+ *
+ * @param gc the graphics context to draw into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static void drawSmallRectangleShadow(Graphics2D gc, int x, int y, int width,
+ int height) {
+ assert Shadow2BottomLeft != null;
+ assert Shadow2TopRight != null;
+ assert Shadow2BottomRight.getWidth(null) == SMALL_SHADOW_SIZE;
+ assert Shadow2BottomRight.getHeight(null) == SMALL_SHADOW_SIZE;
+
+ int blWidth = Shadow2BottomLeft.getWidth(null);
+ int trHeight = Shadow2TopRight.getHeight(null);
+ if (width < blWidth) {
+ return;
+ }
+ if (height < trHeight) {
+ return;
+ }
+
+ gc.drawImage(Shadow2BottomLeft, x - Shadow2BottomLeft.getWidth(null), y + height, null);
+ gc.drawImage(Shadow2BottomRight, x + width, y + height, null);
+ gc.drawImage(Shadow2TopRight, x + width, y, null);
+ gc.drawImage(Shadow2TopLeft, x - Shadow2TopLeft.getWidth(null), y, null);
+ gc.drawImage(Shadow2Bottom,
+ x, y + height, x + width, y + height + Shadow2Bottom.getHeight(null),
+ 0, 0, Shadow2Bottom.getWidth(null), Shadow2Bottom.getHeight(null), null);
+ gc.drawImage(Shadow2Right,
+ x + width, y + Shadow2TopRight.getHeight(null), x + width + Shadow2Right.getWidth(null), y + height,
+ 0, 0, Shadow2Right.getWidth(null), Shadow2Right.getHeight(null), null);
+ gc.drawImage(Shadow2Left,
+ x - Shadow2Left.getWidth(null), y + Shadow2TopLeft.getHeight(null), x, y + height,
+ 0, 0, Shadow2Left.getWidth(null), Shadow2Left.getHeight(null), null);
+ }
+
+ private static Image loadIcon(String name) {
+ InputStream inputStream = ShadowPainter.class.getResourceAsStream(name);
+ if (inputStream == null) {
+ throw new RuntimeException("Unable to load image for shadow: " + name);
+ }
+ try {
+ return ImageIO.read(inputStream);
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to load image for shadow:" + name, e);
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // ignore.
+ }
+ }
+ }
+
+ // Shadow graphics. This was generated by creating a drop shadow in
+ // Gimp, using the parameters x offset=10, y offset=10, blur radius=10,
+ // (for the small drop shadows x offset=10, y offset=10, blur radius=10)
+ // color=black, and opacity=51. These values attempt to make a shadow
+ // that is legible both for dark and light themes, on top of the
+ // canvas background (rgb(150,150,150). Darker shadows would tend to
+ // blend into the foreground for a dark holo screen, and lighter shadows
+ // would be hard to spot on the canvas background. If you make adjustments,
+ // make sure to check the shadow with both dark and light themes.
+ //
+ // After making the graphics, I cut out the top right, bottom left
+ // and bottom right corners as 20x20 images, and these are reproduced by
+ // painting them in the corresponding places in the target graphics context.
+ // I then grabbed a single horizontal gradient line from the middle of the
+ // right edge,and a single vertical gradient line from the bottom. These
+ // are then painted scaled/stretched in the target to fill the gaps between
+ // the three corner images.
+ //
+ // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right
+
+ // Normal Drop Shadow
+ private static final Image ShadowBottom = loadIcon("/icons/shadow-b.png");
+ private static final Image ShadowBottomLeft = loadIcon("/icons/shadow-bl.png");
+ private static final Image ShadowBottomRight = loadIcon("/icons/shadow-br.png");
+ private static final Image ShadowRight = loadIcon("/icons/shadow-r.png");
+ private static final Image ShadowTopRight = loadIcon("/icons/shadow-tr.png");
+ private static final Image ShadowTopLeft = loadIcon("/icons/shadow-tl.png");
+ private static final Image ShadowLeft = loadIcon("/icons/shadow-l.png");
+
+ // Small Drop Shadow
+ private static final Image Shadow2Bottom = loadIcon("/icons/shadow2-b.png");
+ private static final Image Shadow2BottomLeft = loadIcon("/icons/shadow2-bl.png");
+ private static final Image Shadow2BottomRight = loadIcon("/icons/shadow2-br.png");
+ private static final Image Shadow2Right = loadIcon("/icons/shadow2-r.png");
+ private static final Image Shadow2TopRight = loadIcon("/icons/shadow2-tr.png");
+ private static final Image Shadow2TopLeft = loadIcon("/icons/shadow2-tl.png");
+ private static final Image Shadow2Left = loadIcon("/icons/shadow2-l.png");
+}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
new file mode 100644
index 0000000..a6c00f7
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.annotations.NonNull;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.resources.Density;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap_Delegate;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Path_Delegate;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.animation.Transformation;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link ViewGroup}
+ * <p/>
+ * Through the layoutlib_create tool, the original methods of ViewGroup have been replaced by calls
+ * to methods of the same name in this delegate class.
+ */
+public class ViewGroup_Delegate {
+
+ /**
+ * Overrides the original drawChild call in ViewGroup to draw the shadow.
+ */
+ @LayoutlibDelegate
+ /*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child,
+ long drawingTime) {
+ boolean retVal = thisVG.drawChild_Original(canvas, child, drawingTime);
+ if (child.getZ() > thisVG.getZ()) {
+ ViewOutlineProvider outlineProvider = child.getOutlineProvider();
+ Outline outline = new Outline();
+ outlineProvider.getOutline(child, outline);
+
+ if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) {
+ int restoreTo = transformCanvas(thisVG, canvas, child);
+ drawShadow(thisVG, canvas, child, outline);
+ canvas.restoreToCount(restoreTo);
+ }
+ }
+ return retVal;
+ }
+
+ private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
+ Outline outline) {
+ BufferedImage shadow = null;
+ int x = 0;
+ if (outline.mRect != null) {
+ Shadow s = getRectShadow(parent, canvas, child, outline);
+ shadow = s.mShadow;
+ x = -s.mShadowWidth;
+ } else if (outline.mPath != null) {
+ shadow = getPathShadow(child, outline, canvas);
+ }
+ if (shadow == null) {
+ return;
+ }
+ Bitmap bitmap = Bitmap_Delegate.createBitmap(shadow, false,
+ Density.getEnum(canvas.getDensity()));
+ Rect clipBounds = canvas.getClipBounds();
+ Rect newBounds = new Rect(clipBounds);
+ newBounds.left = newBounds.left + x;
+ canvas.clipRect(newBounds, Op.REPLACE);
+ canvas.drawBitmap(bitmap, x, 0, null);
+ canvas.clipRect(clipBounds, Op.REPLACE);
+ }
+
+ private static Shadow getRectShadow(ViewGroup parent, Canvas canvas, View child,
+ Outline outline) {
+ BufferedImage shadow;
+ Rect clipBounds = canvas.getClipBounds();
+ if (clipBounds.isEmpty()) {
+ return null;
+ }
+ float height = child.getZ() - parent.getZ();
+ // Draw large shadow if difference in z index is more than 10dp
+ float largeShadowThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f,
+ getMetrics(child));
+ boolean largeShadow = height > largeShadowThreshold;
+ int shadowSize = largeShadow ? ShadowPainter.SHADOW_SIZE : ShadowPainter.SMALL_SHADOW_SIZE;
+ shadow = new BufferedImage(clipBounds.width() + shadowSize, clipBounds.height(),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphics = shadow.createGraphics();
+ Rect rect = outline.mRect;
+ if (largeShadow) {
+ ShadowPainter.drawRectangleShadow(graphics,
+ rect.left + shadowSize, rect.top, rect.width(), rect.height());
+ } else {
+ ShadowPainter.drawSmallRectangleShadow(graphics,
+ rect.left + shadowSize, rect.top, rect.width(), rect.height());
+ }
+ graphics.dispose();
+ return new Shadow(shadow, shadowSize);
+ }
+
+ @NonNull
+ private static DisplayMetrics getMetrics(View view) {
+ Context context = view.getContext();
+ while (context instanceof ContextThemeWrapper) {
+ context = ((ContextThemeWrapper) context).getBaseContext();
+ }
+ if (context instanceof BridgeContext) {
+ return ((BridgeContext) context).getMetrics();
+ }
+ throw new RuntimeException("View " + view.getClass().getName() + " not created with the " +
+ "right context");
+ }
+
+ private static BufferedImage getPathShadow(View child, Outline outline, Canvas canvas) {
+ Rect clipBounds = canvas.getClipBounds();
+ BufferedImage image = new BufferedImage(clipBounds.width(), clipBounds.height(),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphics = image.createGraphics();
+ graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
+ graphics.dispose();
+ return ShadowPainter.createDropShadow(image, ((int) child.getZ()));
+ }
+
+ // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
+ // which were never taken. Ideally, we should hook up the shadow code in the same method so
+ // that we don't have to transform the canvas twice.
+ private static int transformCanvas(ViewGroup thisVG, Canvas canvas, View child) {
+ final int restoreTo = canvas.save();
+ final boolean childHasIdentityMatrix = child.hasIdentityMatrix();
+ int flags = thisVG.mGroupFlags;
+ Transformation transformToApply = null;
+ boolean concatMatrix = false;
+ if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+ final Transformation t = thisVG.getChildTransformation();
+ final boolean hasTransform = thisVG.getChildStaticTransformation(child, t);
+ if (hasTransform) {
+ final int transformType = t.getTransformationType();
+ transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
+ concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
+ }
+ }
+ concatMatrix |= childHasIdentityMatrix;
+
+ child.computeScroll();
+ int sx = child.mScrollX;
+ int sy = child.mScrollY;
+
+ canvas.translate(child.mLeft - sx, child.mTop - sy);
+ float alpha = child.getAlpha() * child.getTransitionAlpha();
+
+ if (transformToApply != null || alpha < 1 || !childHasIdentityMatrix) {
+ if (transformToApply != null || !childHasIdentityMatrix) {
+ int transX = -sx;
+ int transY = -sy;
+
+ if (transformToApply != null) {
+ if (concatMatrix) {
+ // Undo the scroll translation, apply the transformation matrix,
+ // then redo the scroll translate to get the correct result.
+ canvas.translate(-transX, -transY);
+ canvas.concat(transformToApply.getMatrix());
+ canvas.translate(transX, transY);
+ }
+ if (!childHasIdentityMatrix) {
+ canvas.translate(-transX, -transY);
+ canvas.concat(child.getMatrix());
+ canvas.translate(transX, transY);
+ }
+ }
+
+ }
+ }
+ return restoreTo;
+ }
+
+ private static class Shadow {
+ public BufferedImage mShadow;
+ public int mShadowWidth;
+
+ public Shadow(BufferedImage shadow, int shadowWidth) {
+ mShadow = shadow;
+ mShadowWidth = shadowWidth;
+ }
+
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/WindowCallback.java b/tools/layoutlib/bridge/src/android/view/WindowCallback.java
new file mode 100644
index 0000000..78242a8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/WindowCallback.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.ActionMode.Callback;
+import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * An empty implementation of {@link Window.Callback} that always returns null/false.
+ */
+public class WindowCallback implements Window.Callback {
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ @Override
+ public View onCreatePanelView(int featureId) {
+ return null;
+ }
+
+ @Override
+ public boolean onCreatePanelMenu(int featureId, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onPreparePanel(int featureId, View view, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ return false;
+ }
+
+ @Override
+ public void onWindowAttributesChanged(LayoutParams attrs) {
+
+ }
+
+ @Override
+ public void onContentChanged() {
+
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+
+ }
+
+ @Override
+ public void onPanelClosed(int featureId, Menu menu) {
+
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ return false;
+ }
+
+ @Override
+ public ActionMode onWindowStartingActionMode(Callback callback) {
+ return null;
+ }
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+
+ }
+
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java b/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java
new file mode 100644
index 0000000..1bd9830
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.view.KeyEvent;
+
+/**
+ * Delegate used to provide new implementation of few methods in {@link TimePickerClockDelegate}.
+ */
+public class TimePickerClockDelegate_Delegate {
+
+ // Copied from TimePickerClockDelegate.
+ private static final int AM = 0;
+ private static final int PM = 1;
+
+ @LayoutlibDelegate
+ static int getAmOrPmKeyCode(TimePickerClockDelegate tpcd, int amOrPm) {
+ // We don't care about locales here.
+ if (amOrPm == AM) {
+ return KeyEvent.KEYCODE_A;
+ } else if (amOrPm == PM) {
+ return KeyEvent.KEYCODE_P;
+ } else {
+ assert false : "amOrPm value in TimePickerSpinnerDelegate can only be 0 or 1";
+ return -1;
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/widget/Toolbar_Accessor.java b/tools/layoutlib/bridge/src/android/widget/Toolbar_Accessor.java
new file mode 100644
index 0000000..fdd1779
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/widget/Toolbar_Accessor.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+
+/**
+ * To access non public members of classes in {@link Toolbar}
+ */
+public class Toolbar_Accessor {
+ public static ActionMenuPresenter getActionMenuPresenter(Toolbar toolbar) {
+ return toolbar.getOuterActionMenuPresenter();
+ }
+
+ public static Context getPopupContext(Toolbar toolbar) {
+ return toolbar.getPopupContext();
+ }
+}
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 3d0e1e8..4d2c2fc 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -19,8 +19,10 @@ package com.android.layoutlib.bridge;
import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
+import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.Capability;
import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.Features;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
@@ -35,10 +37,11 @@ import com.android.tools.layoutlib.create.MethodAdapter;
import com.android.tools.layoutlib.create.OverrideMethod;
import com.android.util.Pair;
import com.ibm.icu.util.ULocale;
+import libcore.io.MemoryMappedFile_Delegate;
import android.content.res.BridgeAssetManager;
import android.graphics.Bitmap;
-import android.graphics.Typeface_Accessor;
+import android.graphics.FontFamily_Delegate;
import android.graphics.Typeface_Delegate;
import android.os.Looper;
import android.os.Looper_Accessor;
@@ -178,7 +181,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
*/
private static LayoutLog sCurrentLog = sDefaultLog;
- private EnumSet<Capability> mCapabilities;
+ private static final int LAST_SUPPORTED_FEATURE = Features.PREFERENCES_RENDERING;
@Override
public int getApiLevel() {
@@ -186,8 +189,16 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
}
@Override
+ @Deprecated
public EnumSet<Capability> getCapabilities() {
- return mCapabilities;
+ // The Capability class is deprecated and frozen. All Capabilities enumerated there are
+ // supported by this version of LayoutLibrary. So, it's safe to use EnumSet.allOf()
+ return EnumSet.allOf(Capability.class);
+ }
+
+ @Override
+ public boolean supports(int feature) {
+ return feature <= LAST_SUPPORTED_FEATURE;
}
@Override
@@ -198,26 +209,6 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
sPlatformProperties = platformProperties;
sEnumValueMap = enumValueMap;
- // don't use EnumSet.allOf(), because the bridge doesn't come with its specific version
- // of layoutlib_api. It is provided by the client which could have a more recent version
- // with newer, unsupported capabilities.
- mCapabilities = EnumSet.of(
- Capability.UNBOUND_RENDERING,
- Capability.CUSTOM_BACKGROUND_COLOR,
- Capability.RENDER,
- Capability.LAYOUT_ONLY,
- Capability.EMBEDDED_LAYOUT,
- Capability.VIEW_MANIPULATION,
- Capability.PLAY_ANIMATION,
- Capability.ANIMATED_VIEW_MANIPULATION,
- Capability.ADAPTER_BINDING,
- Capability.EXTENDED_VIEWINFO,
- Capability.FIXED_SCALABLE_NINE_PATCH,
- Capability.RTL,
- Capability.ACTION_BAR,
- Capability.SIMULATE_PLATFORM);
-
-
BridgeAssetManager.initSystem();
// When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
@@ -250,7 +241,8 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
}
// load the fonts.
- Typeface_Delegate.setFontLocation(fontLocation.getAbsolutePath());
+ FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath());
+ MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile());
// now parse com.android.internal.R (and only this one as android.R is a subset of
// the internal version), and put the content in the maps.
@@ -303,7 +295,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
BridgeAssetManager.clearSystem();
// dispose of the default typeface.
- Typeface_Accessor.resetDefaults();
+ Typeface_Delegate.resetDefaults();
return true;
}
@@ -459,7 +451,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
public static void setLog(LayoutLog log) {
// check only the thread currently owning the lock can do this.
- if (sLock.isHeldByCurrentThread() == false) {
+ if (!sLock.isHeldByCurrentThread()) {
throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
}
@@ -489,7 +481,6 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
/**
* Returns the name of a framework resource whose value is an int array.
- * @param array
*/
public static String resolveResourceId(int[] array) {
sIntArrayWrapper.set(array);
@@ -502,6 +493,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
* @param name the name of the resource.
* @return an {@link Integer} containing the resource id, or null if no resource were found.
*/
+ @NonNull
public static Integer getResourceId(ResourceType type, String name) {
Map<String, Integer> map = sRevRMap.get(type);
Integer value = null;
@@ -509,11 +501,8 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
value = map.get(name);
}
- if (value == null) {
- value = sDynamicIds.getId(type, name);
- }
+ return value == null ? sDynamicIds.getId(type, name) : value;
- return value;
}
/**
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
index 89288bf..e4cbb2f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
@@ -90,7 +90,7 @@ public final class BridgeContentProvider implements IContentProvider {
@Override
public ParcelFileDescriptor openFile(
- String callingPackage, Uri arg0, String arg1, ICancellationSignal signal)
+ String callingPackage, Uri arg0, String arg1, ICancellationSignal signal, IBinder token)
throws RemoteException, FileNotFoundException {
// TODO Auto-generated method stub
return null;
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 04a52ea..3953624 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,6 +16,7 @@
package com.android.layoutlib.bridge.android;
+import android.os.IBinder;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.IProjectCallback;
@@ -52,7 +53,6 @@ import android.content.res.BridgeTypedArray;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
@@ -74,6 +74,7 @@ import android.view.DisplayAdjustments;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.view.textservice.TextServicesManager;
import java.io.File;
@@ -93,8 +94,15 @@ import java.util.Map;
*/
public final class BridgeContext extends Context {
- private Resources mSystemResources;
+ /** The map adds cookies to each view so that IDE can link xml tags to views. */
private final HashMap<View, Object> mViewKeyMap = new HashMap<View, Object>();
+ /**
+ * In some cases, when inflating an xml, some objects are created. Then later, the objects are
+ * converted to views. This map stores the mapping from objects to cookies which can then be
+ * used to populate the mViewKeyMap.
+ */
+ private final HashMap<Object, Object> mViewKeyHelpMap = new HashMap<Object, Object>();
+ private Resources mSystemResources;
private final Object mProjectKey;
private final DisplayMetrics mMetrics;
private final RenderResources mRenderResources;
@@ -115,19 +123,19 @@ public final class BridgeContext extends Context {
private int mDynamicIdGenerator = 0x02030000; // Base id for R.style in custom namespace
// cache for TypedArray generated from IStyleResourceValue object
- private Map<int[], Map<Integer, TypedArray>> mTypedArrayCache;
+ private Map<int[], Map<Integer, BridgeTypedArray>> mTypedArrayCache;
private BridgeInflater mBridgeInflater;
private BridgeContentResolver mContentResolver;
private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>();
+ private SharedPreferences mSharedPreferences;
- /**
+ /**
* @param projectKey An Object identifying the project. This is used for the cache mechanism.
* @param metrics the {@link DisplayMetrics}.
* @param renderResources the configured resources (both framework and projects) for this
* render.
- * @param projectCallback
* @param config the Configuration object for this render.
* @param targetSdkVersion the targetSdkVersion of the application.
*/
@@ -191,6 +199,14 @@ public final class BridgeContext extends Context {
return mViewKeyMap.get(view);
}
+ public void addCookie(Object o, Object cookie) {
+ mViewKeyHelpMap.put(o, cookie);
+ }
+
+ public Object getCookie(Object o) {
+ return mViewKeyHelpMap.get(o);
+ }
+
public Object getProjectKey() {
return mProjectKey;
}
@@ -331,7 +347,7 @@ public final class BridgeContext extends Context {
boolean attachToRoot, boolean skipCallbackParser) {
boolean isPlatformLayout = resource.isFramework();
- if (isPlatformLayout == false && skipCallbackParser == false) {
+ if (!isPlatformLayout && !skipCallbackParser) {
// check if the project callback can provide us with a custom parser.
ILayoutPullParser parser = getParser(resource);
@@ -462,12 +478,16 @@ public final class BridgeContext extends Context {
return mDisplayManager;
}
+ if (ACCESSIBILITY_SERVICE.equals(service)) {
+ return AccessibilityManager.getInstance(this);
+ }
+
throw new UnsupportedOperationException("Unsupported Service: " + service);
}
@Override
- public final TypedArray obtainStyledAttributes(int[] attrs) {
+ public final BridgeTypedArray obtainStyledAttributes(int[] attrs) {
// No style is specified here, so create the typed array based on the default theme
// and the styles already applied to it. A null value of style indicates that the default
// theme should be used.
@@ -475,19 +495,30 @@ public final class BridgeContext extends Context {
}
@Override
- public final TypedArray obtainStyledAttributes(int resid, int[] attrs)
+ public final BridgeTypedArray obtainStyledAttributes(int resid, int[] attrs)
throws Resources.NotFoundException {
+ StyleResourceValue style = null;
// get the StyleResourceValue based on the resId;
- StyleResourceValue style = getStyleByDynamicId(resid);
+ if (resid != 0) {
+ style = getStyleByDynamicId(resid);
+
+ if (style == null) {
+ // In some cases, style may not be a dynamic id, so we do a full search.
+ ResourceReference ref = resolveId(resid);
+ if (ref != null) {
+ style = mRenderResources.getStyle(ref.getName(), ref.isFramework());
+ }
+ }
- if (style == null) {
- throw new Resources.NotFoundException();
+ if (style == null) {
+ throw new Resources.NotFoundException();
+ }
}
if (mTypedArrayCache == null) {
- mTypedArrayCache = new HashMap<int[], Map<Integer,TypedArray>>();
+ mTypedArrayCache = new HashMap<int[], Map<Integer,BridgeTypedArray>>();
- Map<Integer, TypedArray> map = new HashMap<Integer, TypedArray>();
+ Map<Integer, BridgeTypedArray> map = new HashMap<Integer, BridgeTypedArray>();
mTypedArrayCache.put(attrs, map);
BridgeTypedArray ta = createStyleBasedTypedArray(style, attrs);
@@ -497,14 +528,14 @@ public final class BridgeContext extends Context {
}
// get the 2nd map
- Map<Integer, TypedArray> map = mTypedArrayCache.get(attrs);
+ Map<Integer, BridgeTypedArray> map = mTypedArrayCache.get(attrs);
if (map == null) {
- map = new HashMap<Integer, TypedArray>();
+ map = new HashMap<Integer, BridgeTypedArray>();
mTypedArrayCache.put(attrs, map);
}
// get the array from the 2nd map
- TypedArray ta = map.get(resid);
+ BridgeTypedArray ta = map.get(resid);
if (ta == null) {
ta = createStyleBasedTypedArray(style, attrs);
@@ -515,12 +546,12 @@ public final class BridgeContext extends Context {
}
@Override
- public final TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs) {
+ public final BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs) {
return obtainStyledAttributes(set, attrs, 0, 0);
}
@Override
- public TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs,
+ public BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs,
int defStyleAttr, int defStyleRes) {
Map<String, String> defaultPropMap = null;
@@ -663,7 +694,7 @@ public final class BridgeContext extends Context {
}
String attrName = attribute.getFirst();
- boolean frameworkAttr = attribute.getSecond().booleanValue();
+ boolean frameworkAttr = attribute.getSecond();
String value = null;
if (set != null) {
value = set.getAttributeValue(
@@ -672,7 +703,7 @@ public final class BridgeContext extends Context {
// if this is an app attribute, and the first get fails, try with the
// new res-auto namespace as well
- if (frameworkAttr == false && value == null) {
+ if (!frameworkAttr && value == null) {
value = set.getAttributeValue(BridgeConstants.NS_APP_RES_AUTO, attrName);
}
}
@@ -789,13 +820,13 @@ public final class BridgeContext extends Context {
List<Pair<String, Boolean>> results = new ArrayList<Pair<String, Boolean>>(attrs.length);
// for each attribute, get its name so that we can search it in the style
- for (int i = 0 ; i < attrs.length ; i++) {
- Pair<ResourceType, String> resolvedResource = Bridge.resolveResourceId(attrs[i]);
+ for (int attr : attrs) {
+ Pair<ResourceType, String> resolvedResource = Bridge.resolveResourceId(attr);
boolean isFramework = false;
if (resolvedResource != null) {
isFramework = true;
} else {
- resolvedResource = mProjectCallback.resolveResourceId(attrs[i]);
+ resolvedResource = mProjectCallback.resolveResourceId(attr);
}
if (resolvedResource != null) {
@@ -841,7 +872,7 @@ public final class BridgeContext extends Context {
if (id == null) {
// generate a new id
- id = Integer.valueOf(++mDynamicIdGenerator);
+ id = ++mDynamicIdGenerator;
// and add it to the maps.
mDynamicIdToStyleMap.put(id, resValue);
@@ -860,19 +891,24 @@ public final class BridgeContext extends Context {
}
public int getFrameworkResourceValue(ResourceType resType, String resName, int defValue) {
- Integer value = Bridge.getResourceId(resType, resName);
- if (value != null) {
- return value.intValue();
+ if (getRenderResources().getFrameworkResource(resType, resName) != null) {
+ // Bridge.getResourceId creates a new resource id if an existing one isn't found. So,
+ // we check for the existence of the resource before calling it.
+ return Bridge.getResourceId(resType, resName);
}
return defValue;
}
public int getProjectResourceValue(ResourceType resType, String resName, int defValue) {
- if (mProjectCallback != null) {
- Integer value = mProjectCallback.getResourceId(resType, resName);
- if (value != null) {
- return value.intValue();
+ // getResourceId creates a new resource id if an existing resource id isn't found. So, we
+ // check for the existence of the resource before calling it.
+ if (getRenderResources().getProjectResource(resType, resName) != null) {
+ if (mProjectCallback != null) {
+ Integer value = mProjectCallback.getResourceId(resType, resName);
+ if (value != null) {
+ return value;
+ }
}
}
@@ -918,12 +954,24 @@ public final class BridgeContext extends Context {
}
@Override
+ public int checkPermission(String arg0, int arg1, int arg2, IBinder arg3) {
+ // pass
+ return 0;
+ }
+
+ @Override
public int checkUriPermission(Uri arg0, int arg1, int arg2, int arg3) {
// pass
return 0;
}
@Override
+ public int checkUriPermission(Uri arg0, int arg1, int arg2, int arg3, IBinder arg4) {
+ // pass
+ return 0;
+ }
+
+ @Override
public int checkUriPermission(Uri arg0, String arg1, String arg2, int arg3,
int arg4, int arg5) {
// pass
@@ -1152,8 +1200,10 @@ public final class BridgeContext extends Context {
@Override
public SharedPreferences getSharedPreferences(String arg0, int arg1) {
- // pass
- return null;
+ if (mSharedPreferences == null) {
+ mSharedPreferences = new BridgeSharedPreferences();
+ }
+ return mSharedPreferences;
}
@Override
@@ -1455,9 +1505,6 @@ public final class BridgeContext extends Context {
return null;
}
- /**
- * @hide
- */
@Override
public int getUserId() {
return 0; // not used
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
index 22265a3..39ebdfc 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -116,11 +116,6 @@ public class BridgePowerManager implements IPowerManager {
}
@Override
- public void setMaximumScreenOffTimeoutFromDeviceAdmin(int arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
public void setStayOnSetting(int arg0) throws RemoteException {
// pass for now.
}
@@ -145,4 +140,9 @@ public class BridgePowerManager implements IPowerManager {
public void wakeUp(long time) throws RemoteException {
// pass for now.
}
+
+ @Override
+ public void boostScreenBrightness(long time) throws RemoteException {
+ // pass for now.
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeSharedPreferences.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeSharedPreferences.java
new file mode 100644
index 0000000..132ff2f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeSharedPreferences.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.android;
+
+import android.content.SharedPreferences;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An empty shared preferences implementation which doesn't store anything. It always returns
+ * null, 0 or false.
+ */
+public class BridgeSharedPreferences implements SharedPreferences {
+ private Editor mEditor;
+
+ @Override
+ public Map<String, ?> getAll() {
+ return null;
+ }
+
+ @Override
+ public String getString(String key, String defValue) {
+ return null;
+ }
+
+ @Override
+ public Set<String> getStringSet(String key, Set<String> defValues) {
+ return null;
+ }
+
+ @Override
+ public int getInt(String key, int defValue) {
+ return 0;
+ }
+
+ @Override
+ public long getLong(String key, long defValue) {
+ return 0;
+ }
+
+ @Override
+ public float getFloat(String key, float defValue) {
+ return 0;
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean defValue) {
+ return false;
+ }
+
+ @Override
+ public boolean contains(String key) {
+ return false;
+ }
+
+ @Override
+ public Editor edit() {
+ if (mEditor != null) {
+ return mEditor;
+ }
+ mEditor = new Editor() {
+ @Override
+ public Editor putString(String key, String value) {
+ return null;
+ }
+
+ @Override
+ public Editor putStringSet(String key, Set<String> values) {
+ return null;
+ }
+
+ @Override
+ public Editor putInt(String key, int value) {
+ return null;
+ }
+
+ @Override
+ public Editor putLong(String key, long value) {
+ return null;
+ }
+
+ @Override
+ public Editor putFloat(String key, float value) {
+ return null;
+ }
+
+ @Override
+ public Editor putBoolean(String key, boolean value) {
+ return null;
+ }
+
+ @Override
+ public Editor remove(String key) {
+ return null;
+ }
+
+ @Override
+ public Editor clear() {
+ return null;
+ }
+
+ @Override
+ public boolean commit() {
+ return false;
+ }
+
+ @Override
+ public void apply() {
+ }
+ };
+ return mEditor;
+ }
+
+ @Override
+ public void registerOnSharedPreferenceChangeListener(
+ OnSharedPreferenceChangeListener listener) {
+ }
+
+ @Override
+ public void unregisterOnSharedPreferenceChangeListener(
+ OnSharedPreferenceChangeListener listener) {
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
index 997b199..4c4454d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -95,6 +95,10 @@ public final class BridgeWindow implements IWindow {
}
@Override
+ public void dispatchWindowShown() {
+ }
+
+ @Override
public IBinder asBinder() {
// pass for now.
return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 0ed6ab1..0f51d00 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -38,7 +38,7 @@ import android.view.WindowManager.LayoutParams;
public final class BridgeWindowSession implements IWindowSession {
@Override
- public int add(IWindow arg0, int seq, LayoutParams arg1, int arg2, Rect arg3,
+ public int add(IWindow arg0, int seq, LayoutParams arg1, int arg2, Rect arg3, Rect arg4,
InputChannel outInputchannel)
throws RemoteException {
// pass for now.
@@ -47,7 +47,7 @@ public final class BridgeWindowSession implements IWindowSession {
@Override
public int addToDisplay(IWindow arg0, int seq, LayoutParams arg1, int arg2, int displayId,
- Rect arg3, InputChannel outInputchannel)
+ Rect arg3, Rect arg4, InputChannel outInputchannel)
throws RemoteException {
// pass for now.
return 0;
@@ -55,7 +55,7 @@ public final class BridgeWindowSession implements IWindowSession {
@Override
public int addWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2,
- Rect arg3)
+ Rect arg3, Rect arg4)
throws RemoteException {
// pass for now.
return 0;
@@ -63,7 +63,7 @@ public final class BridgeWindowSession implements IWindowSession {
@Override
public int addToDisplayWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2,
- int displayId, Rect arg3)
+ int displayId, Rect arg3, Rect arg4)
throws RemoteException {
// pass for now.
return 0;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/SessionParamsFlags.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/SessionParamsFlags.java
new file mode 100644
index 0000000..e00ea6a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/SessionParamsFlags.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.android;
+
+import com.android.ide.common.rendering.api.SessionParams;
+
+/**
+ * This contains all known keys for the {@link SessionParams#getFlag(SessionParams.Key)}.
+ * <p/>
+ * The IDE has its own copy of this class which may be newer or older than this one.
+ * <p/>
+ * Constants should never be modified or removed from this class.
+ */
+public final class SessionParamsFlags {
+
+ public static final SessionParams.Key<String> FLAG_KEY_ROOT_TAG =
+ new SessionParams.Key<String>("rootTag", String.class);
+
+ // Disallow instances.
+ private SessionParamsFlags() {}
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
index d95c815..2ff8d37 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
@@ -18,210 +18,116 @@ package com.android.layoutlib.bridge.bars;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.ide.common.rendering.api.ActionBarCallback;
-import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.internal.R;
-import com.android.internal.app.WindowDecorActionBar;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuItemImpl;
-import com.android.internal.widget.ActionBarAccessor;
-import com.android.internal.widget.ActionBarContainer;
-import com.android.internal.widget.ActionBarView;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.ResourceHelper;
-import com.android.resources.ResourceType;
-import android.app.ActionBar;
-import android.app.ActionBar.Tab;
-import android.app.ActionBar.TabListener;
-import android.app.FragmentTransaction;
import android.content.Context;
import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
-import android.view.Gravity;
import android.view.LayoutInflater;
-import android.view.MenuInflater;
import android.view.View;
+import android.view.View.MeasureSpec;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ActionMenuPresenter;
import android.widget.FrameLayout;
-import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import java.util.ArrayList;
-/**
- * A layout representing the action bar.
- */
-public class ActionBarLayout extends LinearLayout {
+public class ActionBarLayout {
- // Store another reference to the context so that we don't have to cast it repeatedly.
- @NonNull private final BridgeContext mBridgeContext;
- @NonNull private final Context mThemedContext;
-
- @NonNull private final ActionBar mActionBar;
-
- // Data for Action Bar.
- @Nullable private final String mIcon;
- @Nullable private final String mTitle;
- @Nullable private final String mSubTitle;
- private final boolean mSplit;
- private final boolean mShowHomeAsUp;
- private final int mNavMode;
-
- // Helper fields.
- @NonNull private final MenuBuilder mMenuBuilder;
- private final int mPopupMaxWidth;
- @NonNull private final RenderResources res;
- @Nullable private final ActionBarView mActionBarView;
- @Nullable private FrameLayout mContentRoot;
- @NonNull private final ActionBarCallback mCallback;
+ private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout";
- // A fake parent for measuring views.
- @Nullable private ViewGroup mMeasureParent;
+ // The Action Bar
+ @NonNull
+ private CustomActionBarWrapper mActionBar;
- public ActionBarLayout(@NonNull BridgeContext context, @NonNull SessionParams params) {
+ // Store another reference to the context so that we don't have to cast it repeatedly.
+ @NonNull
+ private final BridgeContext mBridgeContext;
- super(context);
- setOrientation(LinearLayout.HORIZONTAL);
- setGravity(Gravity.CENTER_VERTICAL);
+ @NonNull
+ private FrameLayout mContentRoot;
- // Inflate action bar layout.
- LayoutInflater.from(context).inflate(R.layout.screen_action_bar, this,
- true /*attachToRoot*/);
- mActionBar = new WindowDecorActionBar(this);
+ // A fake parent for measuring views.
+ @Nullable
+ private ViewGroup mMeasureParent;
- // Set contexts.
- mBridgeContext = context;
- mThemedContext = mActionBar.getThemedContext();
-
- // Set data for action bar.
- mCallback = params.getProjectCallback().getActionBarCallback();
- mIcon = params.getAppIcon();
- mTitle = params.getAppLabel();
- // Split Action Bar when the screen size is narrow and the application requests split action
- // bar when narrow.
- mSplit = context.getResources().getBoolean(R.bool.split_action_bar_is_narrow) &&
- mCallback.getSplitActionBarWhenNarrow();
- mNavMode = mCallback.getNavigationMode();
- // TODO: Support Navigation Drawer Indicator.
- mShowHomeAsUp = mCallback.getHomeButtonStyle() == HomeButtonStyle.SHOW_HOME_AS_UP;
- mSubTitle = mCallback.getSubTitle();
-
-
- // Set helper fields.
- mMenuBuilder = new MenuBuilder(mThemedContext);
- res = mBridgeContext.getRenderResources();
- mPopupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2,
- mThemedContext.getResources().getDimensionPixelSize(
- R.dimen.config_prefDialogWidth));
- mActionBarView = (ActionBarView) findViewById(R.id.action_bar);
- mContentRoot = (FrameLayout) findViewById(android.R.id.content);
-
- setupActionBar();
- }
+ // A Layout that contains the inflated action bar. The menu popup is added to this layout.
+ @NonNull
+ private final RelativeLayout mEnclosingLayout;
/**
- * Sets up the action bar by filling the appropriate data.
+ * Inflate the action bar and attach it to {@code parentView}
*/
- private void setupActionBar() {
- // Add title and sub title.
- ResourceValue titleValue = res.findResValue(mTitle, false /*isFramework*/);
- if (titleValue != null && titleValue.getValue() != null) {
- mActionBar.setTitle(titleValue.getValue());
- } else {
- mActionBar.setTitle(mTitle);
- }
- if (mSubTitle != null) {
- mActionBar.setSubtitle(mSubTitle);
- }
+ public ActionBarLayout(@NonNull BridgeContext context, @NonNull SessionParams params,
+ @NonNull ViewGroup parentView) {
- // Add show home as up icon.
- if (mShowHomeAsUp) {
- mActionBar.setDisplayOptions(0xFF, ActionBar.DISPLAY_HOME_AS_UP);
- }
+ mBridgeContext = context;
- // Set the navigation mode.
- mActionBar.setNavigationMode(mNavMode);
- if (mNavMode == ActionBar.NAVIGATION_MODE_TABS) {
- setupTabs(3);
+ ResourceValue layoutName = context.getRenderResources()
+ .findItemInTheme(LAYOUT_ATTR_NAME, true);
+ if (layoutName != null) {
+ // We may need to resolve the reference obtained.
+ layoutName = context.getRenderResources().findResValue(layoutName.getValue(),
+ layoutName.isFramework());
}
-
- if (mActionBarView != null) {
- // If the action bar style doesn't specify an icon, set the icon obtained from the session
- // params.
- if (!mActionBarView.hasIcon() && mIcon != null) {
- Drawable iconDrawable = getDrawable(mIcon, false /*isFramework*/);
- if (iconDrawable != null) {
- mActionBar.setIcon(iconDrawable);
- }
+ int layoutId = 0;
+ String error = null;
+ if (layoutName == null) {
+ error = "Unable to find action bar layout (" + LAYOUT_ATTR_NAME
+ + ") in the current theme.";
+ } else {
+ layoutId = context.getFrameworkResourceValue(layoutName.getResourceType(),
+ layoutName.getName(), 0);
+ if (layoutId == 0) {
+ error = String.format("Unable to resolve attribute \"%s\" of type \"%s\"",
+ layoutName.getName(), layoutName.getResourceType());
}
+ }
+ if (layoutId == 0) {
+ throw new RuntimeException(error);
+ }
+ // Create a RelativeLayout to hold the action bar. The layout is needed so that we may
+ // add the menu popup to it.
+ mEnclosingLayout = new RelativeLayout(mBridgeContext);
+ setMatchParent(mEnclosingLayout);
+ parentView.addView(mEnclosingLayout);
- // Set action bar to be split, if needed.
- ActionBarContainer splitView = (ActionBarContainer) findViewById(R.id.split_action_bar);
- mActionBarView.setSplitView(splitView);
- mActionBarView.setSplitToolbar(mSplit);
+ // Inflate action bar layout.
+ View decorContent = LayoutInflater.from(context).inflate(layoutId, mEnclosingLayout, true);
- inflateMenus();
- }
- }
+ mActionBar = CustomActionBarWrapper.getActionBarWrapper(context, params, decorContent);
- /**
- * Gets the menus to add to the action bar from the callback, resolves them, inflates them and
- * adds them to the action bar.
- */
- private void inflateMenus() {
- if (mActionBarView == null) {
- return;
- }
- final MenuInflater inflater = new MenuInflater(mThemedContext);
- for (String name : mCallback.getMenuIdNames()) {
- if (mBridgeContext.getRenderResources().getProjectResource(ResourceType.MENU, name)
- != null) {
- int id = mBridgeContext.getProjectResourceValue(ResourceType.MENU, name, -1);
- if (id > -1) {
- inflater.inflate(id, mMenuBuilder);
- }
- }
- }
- mActionBarView.setMenu(mMenuBuilder, null /*callback*/);
- }
+ FrameLayout contentRoot = (FrameLayout) mEnclosingLayout.findViewById(android.R.id.content);
- // TODO: Use an adapter, like List View to set up tabs.
- private void setupTabs(int num) {
- for (int i = 1; i <= num; i++) {
- Tab tab = mActionBar.newTab().setText("Tab" + i).setTabListener(new TabListener() {
- @Override
- public void onTabUnselected(Tab t, FragmentTransaction ft) {
- // pass
- }
- @Override
- public void onTabSelected(Tab t, FragmentTransaction ft) {
- // pass
- }
- @Override
- public void onTabReselected(Tab t, FragmentTransaction ft) {
- // pass
- }
- });
- mActionBar.addTab(tab);
+ // If something went wrong and we were not able to initialize the content root,
+ // just add a frame layout inside this and return.
+ if (contentRoot == null) {
+ contentRoot = new FrameLayout(context);
+ setMatchParent(contentRoot);
+ mEnclosingLayout.addView(contentRoot);
+ mContentRoot = contentRoot;
+ } else {
+ mContentRoot = contentRoot;
+ mActionBar.setupActionBar();
+ mActionBar.inflateMenus();
}
}
- @Nullable
- private Drawable getDrawable(@NonNull String name, boolean isFramework) {
- ResourceValue value = res.findResValue(name, isFramework);
- value = res.resolveResValue(value);
- if (value != null) {
- return ResourceHelper.getDrawable(value, mBridgeContext);
- }
- return null;
+ private void setMatchParent(View view) {
+ view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
}
/**
@@ -229,73 +135,53 @@ public class ActionBarLayout extends LinearLayout {
* the content frame which shall serve as the new content root.
*/
public void createMenuPopup() {
- assert mContentRoot != null && findViewById(android.R.id.content) == mContentRoot
+ assert mEnclosingLayout.getChildCount() == 1
: "Action Bar Menus have already been created.";
if (!isOverflowPopupNeeded()) {
return;
}
- // Create a layout to hold the menus and the user's content.
- RelativeLayout layout = new RelativeLayout(mThemedContext);
- layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- mContentRoot.addView(layout);
- // Create a layout for the user's content.
- FrameLayout contentRoot = new FrameLayout(mBridgeContext);
- contentRoot.setLayoutParams(new LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- // Add contentRoot and menus to the layout.
- layout.addView(contentRoot);
- layout.addView(createMenuView());
- // ContentRoot is now the view we just created.
- mContentRoot = contentRoot;
- }
-
- /**
- * Returns a {@link LinearLayout} containing the menu list view to be embedded in a
- * {@link RelativeLayout}
- */
- @NonNull
- private View createMenuView() {
DisplayMetrics metrics = mBridgeContext.getMetrics();
- OverflowMenuAdapter adapter = new OverflowMenuAdapter(mMenuBuilder, mThemedContext);
+ MenuBuilder menu = mActionBar.getMenuBuilder();
+ OverflowMenuAdapter adapter = new OverflowMenuAdapter(menu, mActionBar.getPopupContext());
- LinearLayout layout = new LinearLayout(mThemedContext);
+ ListView listView = new ListView(mActionBar.getPopupContext(), null,
+ R.attr.dropDownListViewStyle);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
measureContentWidth(adapter), LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
- if (mSplit) {
+ if (mActionBar.isSplit()) {
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
- // TODO: Find correct value instead of hardcoded 10dp.
- layoutParams.bottomMargin = getPixelValue("-10dp", metrics);
+ layoutParams.bottomMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
} else {
- layoutParams.topMargin = getPixelValue("-10dp", metrics);
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ layoutParams.topMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
}
- layout.setLayoutParams(layoutParams);
- final TypedArray a = mThemedContext.obtainStyledAttributes(null,
+ layoutParams.setMarginEnd(getPixelValue("5dp", metrics));
+ listView.setLayoutParams(layoutParams);
+ listView.setAdapter(adapter);
+ final TypedArray a = mActionBar.getPopupContext().obtainStyledAttributes(null,
R.styleable.PopupWindow, R.attr.popupMenuStyle, 0);
- layout.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
- layout.setDividerDrawable(a.getDrawable(R.attr.actionBarDivider));
+ listView.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
+ listView.setDivider(a.getDrawable(R.attr.actionBarDivider));
a.recycle();
- layout.setOrientation(LinearLayout.VERTICAL);
- layout.setDividerPadding(getPixelValue("12dp", metrics));
- layout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
-
- ListView listView = new ListView(mThemedContext, null, R.attr.dropDownListViewStyle);
- listView.setAdapter(adapter);
- layout.addView(listView);
- return layout;
+ listView.setElevation(mActionBar.getMenuPopupElevation());
+ mEnclosingLayout.addView(listView);
}
private boolean isOverflowPopupNeeded() {
- boolean needed = mCallback.isOverflowPopupNeeded();
+ boolean needed = mActionBar.isOverflowPopupNeeded();
if (!needed) {
return false;
}
// Copied from android.widget.ActionMenuPresenter.updateMenuView()
- ArrayList<MenuItemImpl> menus = mMenuBuilder.getNonActionItems();
- if (ActionBarAccessor.getActionMenuPresenter(mActionBarView).isOverflowReserved() &&
+ ArrayList<MenuItemImpl> menus = mActionBar.getMenuBuilder().getNonActionItems();
+ ActionMenuPresenter presenter = mActionBar.getActionMenuPresenter();
+ if (presenter == null) {
+ throw new RuntimeException("Failed to create a Presenter for Action Bar Menus.");
+ }
+ if (presenter.isOverflowReserved() &&
menus != null) {
final int count = menus.size();
if (count == 1) {
@@ -307,7 +193,7 @@ public class ActionBarLayout extends LinearLayout {
return needed;
}
- @Nullable
+ @NonNull
public FrameLayout getContentRoot() {
return mContentRoot;
}
@@ -319,6 +205,7 @@ public class ActionBarLayout extends LinearLayout {
View itemView = null;
int itemType = 0;
+ Context context = mActionBar.getPopupContext();
final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final int count = adapter.getCount();
@@ -330,15 +217,17 @@ public class ActionBarLayout extends LinearLayout {
}
if (mMeasureParent == null) {
- mMeasureParent = new FrameLayout(mThemedContext);
+ mMeasureParent = new FrameLayout(context);
}
itemView = adapter.getView(i, itemView, mMeasureParent);
itemView.measure(widthMeasureSpec, heightMeasureSpec);
final int itemWidth = itemView.getMeasuredWidth();
- if (itemWidth >= mPopupMaxWidth) {
- return mPopupMaxWidth;
+ int popupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2,
+ context.getResources().getDimensionPixelSize(R.dimen.config_prefDialogWidth));
+ if (itemWidth >= popupMaxWidth) {
+ return popupMaxWidth;
} else if (itemWidth > maxWidth) {
maxWidth = itemWidth;
}
@@ -347,9 +236,30 @@ public class ActionBarLayout extends LinearLayout {
return maxWidth;
}
- private int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
+ static int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/);
return (int) typedValue.getDimension(metrics);
}
+ // TODO: This is duplicated from RenderSessionImpl.
+ private int getActionBarHeight() {
+ RenderResources resources = mBridgeContext.getRenderResources();
+ DisplayMetrics metrics = mBridgeContext.getMetrics();
+ ResourceValue value = resources.findItemInTheme("actionBarSize", true);
+
+ // resolve it
+ value = resources.resolveResValue(value);
+
+ if (value != null) {
+ // get the numerical value, if available
+ TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
+ true);
+ if (typedValue != null) {
+ // compute the pixel value based on the display metrics
+ return (int) typedValue.getDimension(metrics);
+
+ }
+ }
+ return 0;
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java
new file mode 100644
index 0000000..6db722e
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.bars;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ActionBarCallback;
+import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.internal.R;
+import com.android.internal.app.ToolbarActionBar;
+import com.android.internal.app.WindowDecorActionBar;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.widget.ActionBarAccessor;
+import com.android.internal.widget.ActionBarView;
+import com.android.internal.widget.DecorToolbar;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.ActionBar.TabListener;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowCallback;
+import android.widget.ActionMenuPresenter;
+import android.widget.Toolbar;
+import android.widget.Toolbar_Accessor;
+
+import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.resources.ResourceType.MENU;
+
+/**
+ * A common API to access {@link ToolbarActionBar} and {@link WindowDecorActionBar}.
+ */
+public abstract class CustomActionBarWrapper {
+
+ @NonNull protected ActionBar mActionBar;
+ @NonNull protected SessionParams mParams;
+ @NonNull protected ActionBarCallback mCallback;
+ @NonNull protected BridgeContext mContext;
+
+ /**
+ * Returns a wrapper around different implementations of the Action Bar to provide a common API.
+ *
+ * @param decorContent the top level view returned by inflating
+ * ?attr/windowActionBarFullscreenDecorLayout
+ */
+ @NonNull
+ public static CustomActionBarWrapper getActionBarWrapper(@NonNull BridgeContext context,
+ @NonNull SessionParams params, @NonNull View decorContent) {
+ View view = decorContent.findViewById(R.id.action_bar);
+ if (view instanceof Toolbar) {
+ return new ToolbarWrapper(context, params, ((Toolbar) view));
+ } else if (view instanceof ActionBarView) {
+ return new WindowActionBarWrapper(context, params, decorContent,
+ ((ActionBarView) view));
+ } else {
+ throw new IllegalStateException("Can't make an action bar out of " +
+ view.getClass().getSimpleName());
+ }
+ }
+
+ CustomActionBarWrapper(@NonNull BridgeContext context, @NonNull SessionParams params,
+ @NonNull ActionBar actionBar) {
+ mActionBar = actionBar;
+ mParams = params;
+ mCallback = params.getProjectCallback().getActionBarCallback();
+ mContext = context;
+ }
+
+ protected void setupActionBar() {
+ // Do the things that are common to all implementations.
+ RenderResources res = mContext.getRenderResources();
+
+ String title = mParams.getAppLabel();
+ ResourceValue titleValue = res.findResValue(title, false);
+ if (titleValue != null && titleValue.getValue() != null) {
+ mActionBar.setTitle(titleValue.getValue());
+ } else {
+ mActionBar.setTitle(title);
+ }
+
+ String subTitle = mCallback.getSubTitle();
+ if (subTitle != null) {
+ mActionBar.setSubtitle(subTitle);
+ }
+
+ // Add show home as up icon.
+ if (mCallback.getHomeButtonStyle() == HomeButtonStyle.SHOW_HOME_AS_UP) {
+ mActionBar.setDisplayOptions(0xFF, ActionBar.DISPLAY_HOME_AS_UP);
+ }
+ }
+
+ protected boolean isSplit() {
+ return getDecorToolbar().isSplit();
+ }
+
+ protected boolean isOverflowPopupNeeded() {
+ return mCallback.isOverflowPopupNeeded();
+ }
+
+ /**
+ * Gets the menus to add to the action bar from the callback, resolves them, inflates them and
+ * adds them to the action bar.
+ */
+ protected void inflateMenus() {
+ MenuInflater inflater = new MenuInflater(getActionMenuContext());
+ MenuBuilder menuBuilder = getMenuBuilder();
+ for (String name : mCallback.getMenuIdNames()) {
+ int id;
+ if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
+ // Framework menu.
+ name = name.substring(ANDROID_NS_NAME_PREFIX.length());
+ id = mContext.getFrameworkResourceValue(MENU, name, -1);
+ } else {
+ // Project menu.
+ id = mContext.getProjectResourceValue(MENU, name, -1);
+ }
+ if (id > -1) {
+ inflater.inflate(id, menuBuilder);
+ }
+ }
+ }
+
+ /**
+ * The context used for the ActionBar and the menus in the ActionBarView.
+ */
+ @NonNull
+ protected Context getActionMenuContext() {
+ return mActionBar.getThemedContext();
+ }
+
+ /**
+ * The context used to inflate the popup menu.
+ */
+ @NonNull
+ abstract Context getPopupContext();
+
+ /**
+ * The Menu in which to inflate the user's menus.
+ */
+ @NonNull
+ abstract MenuBuilder getMenuBuilder();
+
+ @Nullable
+ abstract ActionMenuPresenter getActionMenuPresenter();
+
+ /**
+ * Framework's wrapper over two ActionBar implementations.
+ */
+ @NonNull
+ abstract DecorToolbar getDecorToolbar();
+
+ abstract int getMenuPopupElevation();
+
+ /**
+ * Margin between the menu popup and the action bar.
+ */
+ abstract int getMenuPopupMargin();
+
+ // ---- The implementations ----
+
+ /**
+ * Material theme uses {@link Toolbar} as the action bar. This wrapper provides access to
+ * Toolbar using a common API.
+ */
+ private static class ToolbarWrapper extends CustomActionBarWrapper {
+
+ @NonNull
+ private final Toolbar mToolbar; // This is the view.
+
+ ToolbarWrapper(@NonNull BridgeContext context, @NonNull SessionParams params,
+ @NonNull Toolbar toolbar) {
+ super(context, params, new ToolbarActionBar(toolbar, "", new WindowCallback())
+ );
+ mToolbar = toolbar;
+ }
+
+ @Override
+ protected void inflateMenus() {
+ super.inflateMenus();
+ // Inflating the menus doesn't initialize the ActionMenuPresenter. Setting a fake menu
+ // and then setting it back does the trick.
+ MenuBuilder menu = getMenuBuilder();
+ DecorToolbar decorToolbar = getDecorToolbar();
+ decorToolbar.setMenu(new MenuBuilder(getActionMenuContext()), null);
+ decorToolbar.setMenu(menu, null);
+ }
+
+ @NonNull
+ @Override
+ Context getPopupContext() {
+ return Toolbar_Accessor.getPopupContext(mToolbar);
+ }
+
+ @NonNull
+ @Override
+ MenuBuilder getMenuBuilder() {
+ return (MenuBuilder) mToolbar.getMenu();
+ }
+
+ @Nullable
+ @Override
+ ActionMenuPresenter getActionMenuPresenter() {
+ return Toolbar_Accessor.getActionMenuPresenter(mToolbar);
+ }
+
+ @NonNull
+ @Override
+ DecorToolbar getDecorToolbar() {
+ return mToolbar.getWrapper();
+ }
+
+ @Override
+ int getMenuPopupElevation() {
+ return 10;
+ }
+
+ @Override
+ int getMenuPopupMargin() {
+ return 0;
+ }
+ }
+
+ /**
+ * Holo theme uses {@link WindowDecorActionBar} as the action bar. This wrapper provides
+ * access to it using a common API.
+ */
+ private static class WindowActionBarWrapper extends CustomActionBarWrapper {
+
+ @NonNull
+ private final WindowDecorActionBar mActionBar;
+ @NonNull
+ private final ActionBarView mActionBarView;
+ @NonNull
+ private final View mDecorContentRoot;
+ private MenuBuilder mMenuBuilder;
+
+ public WindowActionBarWrapper(@NonNull BridgeContext context, @NonNull SessionParams params,
+ @NonNull View decorContentRoot, @NonNull ActionBarView actionBarView) {
+ super(context, params, new WindowDecorActionBar(decorContentRoot));
+ mActionBarView = actionBarView;
+ mActionBar = ((WindowDecorActionBar) super.mActionBar);
+ mDecorContentRoot = decorContentRoot;
+ }
+
+ @Override
+ protected void setupActionBar() {
+ super.setupActionBar();
+
+ // Set the navigation mode.
+ int navMode = mCallback.getNavigationMode();
+ mActionBar.setNavigationMode(navMode);
+ //noinspection deprecation
+ if (navMode == ActionBar.NAVIGATION_MODE_TABS) {
+ setupTabs(3);
+ }
+
+ String icon = mParams.getAppIcon();
+ // If the action bar style doesn't specify an icon, set the icon obtained from the
+ // session params.
+ if (!mActionBar.hasIcon() && icon != null) {
+ Drawable iconDrawable = getDrawable(icon, false);
+ if (iconDrawable != null) {
+ mActionBar.setIcon(iconDrawable);
+ }
+ }
+
+ // Set action bar to be split, if needed.
+ ViewGroup splitView = (ViewGroup) mDecorContentRoot.findViewById(R.id.split_action_bar);
+ if (splitView != null) {
+ mActionBarView.setSplitView(splitView);
+ Resources res = mContext.getResources();
+ boolean split = res.getBoolean(R.bool.split_action_bar_is_narrow)
+ && mCallback.getSplitActionBarWhenNarrow();
+ mActionBarView.setSplitToolbar(split);
+ }
+ }
+
+ @Override
+ protected void inflateMenus() {
+ super.inflateMenus();
+ // The super implementation doesn't set the menu on the view. Set it here.
+ mActionBarView.setMenu(getMenuBuilder(), null);
+ }
+
+ @NonNull
+ @Override
+ Context getPopupContext() {
+ return getActionMenuContext();
+ }
+
+ @NonNull
+ @Override
+ MenuBuilder getMenuBuilder() {
+ if (mMenuBuilder == null) {
+ mMenuBuilder = new MenuBuilder(getActionMenuContext());
+ }
+ return mMenuBuilder;
+ }
+
+ @Nullable
+ @Override
+ ActionMenuPresenter getActionMenuPresenter() {
+ return ActionBarAccessor.getActionMenuPresenter(mActionBarView);
+ }
+
+ @NonNull
+ @Override
+ ActionBarView getDecorToolbar() {
+ return mActionBarView;
+ }
+
+ @Override
+ int getMenuPopupElevation() {
+ return 0;
+ }
+
+ @Override
+ int getMenuPopupMargin() {
+ return -ActionBarLayout.getPixelValue("10dp", mContext.getMetrics());
+ }
+
+ // TODO: Use an adapter, like List View to set up tabs.
+ @SuppressWarnings("deprecation") // For Tab
+ private void setupTabs(int num) {
+ for (int i = 1; i <= num; i++) {
+ Tab tab = mActionBar.newTab().setText("Tab" + i).setTabListener(new TabListener() {
+ @Override
+ public void onTabUnselected(Tab t, FragmentTransaction ft) {
+ // pass
+ }
+ @Override
+ public void onTabSelected(Tab t, FragmentTransaction ft) {
+ // pass
+ }
+ @Override
+ public void onTabReselected(Tab t, FragmentTransaction ft) {
+ // pass
+ }
+ });
+ mActionBar.addTab(tab);
+ }
+ }
+
+ @Nullable
+ private Drawable getDrawable(@NonNull String name, boolean isFramework) {
+ RenderResources res = mContext.getRenderResources();
+ ResourceValue value = res.findResValue(name, isFramework);
+ value = res.resolveResValue(value);
+ if (value != null) {
+ return ResourceHelper.getDrawable(value, mContext);
+ }
+ return null;
+ }
+
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
index b677131..669e6b5 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
@@ -57,63 +57,59 @@ public class RenderDrawable extends RenderAction<DrawableParams> {
public Result render() {
checkLock();
- try {
- // get the drawable resource value
- DrawableParams params = getParams();
- HardwareConfig hardwareConfig = params.getHardwareConfig();
- ResourceValue drawableResource = params.getDrawable();
-
- // resolve it
- BridgeContext context = getContext();
- drawableResource = context.getRenderResources().resolveResValue(drawableResource);
-
- if (drawableResource == null ||
- drawableResource.getResourceType() != ResourceType.DRAWABLE) {
- return Status.ERROR_NOT_A_DRAWABLE.createResult();
- }
+ // get the drawable resource value
+ DrawableParams params = getParams();
+ HardwareConfig hardwareConfig = params.getHardwareConfig();
+ ResourceValue drawableResource = params.getDrawable();
+
+ // resolve it
+ BridgeContext context = getContext();
+ drawableResource = context.getRenderResources().resolveResValue(drawableResource);
+
+ if (drawableResource == null ||
+ drawableResource.getResourceType() != ResourceType.DRAWABLE) {
+ return Status.ERROR_NOT_A_DRAWABLE.createResult();
+ }
- // create a simple FrameLayout
- FrameLayout content = new FrameLayout(context);
+ // create a simple FrameLayout
+ FrameLayout content = new FrameLayout(context);
- // get the actual Drawable object to draw
- Drawable d = ResourceHelper.getDrawable(drawableResource, context);
- content.setBackground(d);
+ // get the actual Drawable object to draw
+ Drawable d = ResourceHelper.getDrawable(drawableResource, context);
+ content.setBackground(d);
- // set the AttachInfo on the root view.
- AttachInfo_Accessor.setAttachInfo(content);
+ // set the AttachInfo on the root view.
+ AttachInfo_Accessor.setAttachInfo(content);
- // measure
- int w = hardwareConfig.getScreenWidth();
- int h = hardwareConfig.getScreenHeight();
- int w_spec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY);
- int h_spec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
- content.measure(w_spec, h_spec);
+ // measure
+ int w = hardwareConfig.getScreenWidth();
+ int h = hardwareConfig.getScreenHeight();
+ int w_spec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY);
+ int h_spec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
+ content.measure(w_spec, h_spec);
- // now do the layout.
- content.layout(0, 0, w, h);
+ // now do the layout.
+ content.layout(0, 0, w, h);
- // preDraw setup
- AttachInfo_Accessor.dispatchOnPreDraw(content);
+ // preDraw setup
+ AttachInfo_Accessor.dispatchOnPreDraw(content);
- // draw into a new image
- BufferedImage image = getImage(w, h);
+ // draw into a new image
+ BufferedImage image = getImage(w, h);
- // create an Android bitmap around the BufferedImage
- Bitmap bitmap = Bitmap_Delegate.createBitmap(image,
- true /*isMutable*/, hardwareConfig.getDensity());
+ // create an Android bitmap around the BufferedImage
+ Bitmap bitmap = Bitmap_Delegate.createBitmap(image,
+ true /*isMutable*/, hardwareConfig.getDensity());
- // create a Canvas around the Android bitmap
- Canvas canvas = new Canvas(bitmap);
- canvas.setDensity(hardwareConfig.getDensity().getDpiValue());
+ // create a Canvas around the Android bitmap
+ Canvas canvas = new Canvas(bitmap);
+ canvas.setDensity(hardwareConfig.getDensity().getDpiValue());
- // and draw
- content.draw(canvas);
+ // and draw
+ content.draw(canvas);
- return Status.SUCCESS.createResult(image);
- } catch (IOException e) {
- return ERROR_UNKNOWN.createResult(e.getMessage(), e);
- }
+ return Status.SUCCESS.createResult(image);
}
protected BufferedImage getImage(int w, int h) {
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
index b8dce70..4637bfd 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -36,6 +36,7 @@ import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.Result.Status;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.common.rendering.api.ViewType;
import com.android.internal.util.XmlUtils;
@@ -49,6 +50,7 @@ import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.SessionParamsFlags;
import com.android.layoutlib.bridge.bars.Config;
import com.android.layoutlib.bridge.bars.NavigationBar;
import com.android.layoutlib.bridge.bars.StatusBar;
@@ -73,6 +75,7 @@ import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.preference.Preference_Delegate;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.AttachInfo_Accessor;
@@ -87,7 +90,6 @@ import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewParent;
import android.view.WindowManagerGlobal_Delegate;
-import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AbsSpinner;
import android.widget.ActionMenuView;
@@ -133,6 +135,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
private int mMeasuredScreenHeight = -1;
private boolean mIsAlphaChannelImage;
private boolean mWindowIsFloating;
+ private Boolean mIsThemeAppCompat;
private int mStatusBarSize;
private int mNavigationBarSize;
@@ -193,11 +196,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
DisplayMetrics metrics = getContext().getMetrics();
// use default of true in case it's not found to use alpha by default
- mIsAlphaChannelImage = getBooleanThemeValue(resources,
- "windowIsFloating", true /*defaultValue*/);
-
- mWindowIsFloating = getBooleanThemeValue(resources, "windowIsFloating",
- true /*defaultValue*/);
+ mIsAlphaChannelImage = getBooleanThemeValue(resources, "windowIsFloating", true, true);
+ // FIXME: Find out why both variables are taking the same value.
+ mWindowIsFloating = getBooleanThemeValue(resources, "windowIsFloating", true, true);
findBackground(resources);
findStatusBar(resources, metrics);
@@ -353,8 +354,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
// if the theme says no title/action bar, then the size will be 0
if (mActionBarSize > 0) {
- ActionBarLayout actionBar = createActionBar(context, params);
- backgroundLayout.addView(actionBar);
+ ActionBarLayout actionBar = createActionBar(context, params, backgroundLayout);
actionBar.createMenuPopup();
mContentRoot = actionBar.getContentRoot();
} else if (mTitleBarSize > 0) {
@@ -398,7 +398,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
// it can instantiate the custom Fragment.
Fragment_Delegate.setProjectCallback(params.getProjectCallback());
- View view = mInflater.inflate(mBlockParser, mContentRoot);
+ String rootTag = params.getFlag(SessionParamsFlags.FLAG_KEY_ROOT_TAG);
+ boolean isPreference = "PreferenceScreen".equals(rootTag);
+ View view;
+ if (isPreference) {
+ view = Preference_Delegate.inflatePreference(getContext(), mBlockParser,
+ mContentRoot);
+ } else {
+ view = mInflater.inflate(mBlockParser, mContentRoot);
+ }
// done with the parser, pop it.
context.popParser();
@@ -409,7 +417,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
AttachInfo_Accessor.setAttachInfo(mViewRoot);
// post-inflate process. For now this supports TabHost/TabWidget
- postInflateProcess(view, params.getProjectCallback());
+ postInflateProcess(view, params.getProjectCallback(), isPreference ? view : null);
// get the background drawable
if (mWindowBackground != null) {
@@ -1051,7 +1059,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
private void findStatusBar(RenderResources resources, DisplayMetrics metrics) {
boolean windowFullscreen = getBooleanThemeValue(resources,
- "windowFullscreen", false /*defaultValue*/);
+ "windowFullscreen", false, !isThemeAppCompat(resources));
if (!windowFullscreen && !mWindowIsFloating) {
// default value
@@ -1078,7 +1086,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
boolean windowActionBar = getBooleanThemeValue(resources,
- "windowActionBar", true /*defaultValue*/);
+ "windowActionBar", true, !isThemeAppCompat(resources));
// if there's a value and it's false (default is true)
if (windowActionBar) {
@@ -1105,7 +1113,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
} else {
// action bar overrides title bar so only look for this one if action bar is hidden
boolean windowNoTitle = getBooleanThemeValue(resources,
- "windowNoTitle", false /*defaultValue*/);
+ "windowNoTitle", false, !isThemeAppCompat(resources));
if (!windowNoTitle) {
@@ -1177,20 +1185,30 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
}
+ private boolean isThemeAppCompat(RenderResources resources) {
+ // Ideally, we should check if the corresponding activity extends
+ // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
+ if (mIsThemeAppCompat == null) {
+ StyleResourceValue defaultTheme = resources.getDefaultTheme();
+ StyleResourceValue val = resources.getStyle("Theme.AppCompat", false);
+ mIsThemeAppCompat = defaultTheme == val || resources.themeIsParentOf(val, defaultTheme);
+ }
+ return mIsThemeAppCompat;
+ }
+
/**
- * Looks for a attribute in the current theme. The attribute is in the android
- * namespace.
+ * Looks for an attribute in the current theme.
*
* @param resources the render resources
* @param name the name of the attribute
* @param defaultValue the default value.
+ * @param isFrameworkAttr if the attribute is in android namespace
* @return the value of the attribute or the default one if not found.
*/
private boolean getBooleanThemeValue(RenderResources resources,
- String name, boolean defaultValue) {
+ String name, boolean defaultValue, boolean isFrameworkAttr) {
- // get the title bar flag from the current theme.
- ResourceValue value = resources.findItemInTheme(name, true /*isFrameworkAttr*/);
+ ResourceValue value = resources.findItemInTheme(name, isFrameworkAttr);
// because it may reference something else, we resolve it.
value = resources.resolveResValue(value);
@@ -1211,12 +1229,16 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
* based on the content of the {@link FrameLayout}.
* @param view the root view to process.
* @param projectCallback callback to the project.
+ * @param skip the view and it's children are not processed.
*/
@SuppressWarnings("deprecation") // For the use of Pair
- private void postInflateProcess(View view, IProjectCallback projectCallback)
+ private void postInflateProcess(View view, IProjectCallback projectCallback, View skip)
throws PostInflateException {
+ if (view == skip) {
+ return;
+ }
if (view instanceof TabHost) {
- setupTabHost((TabHost)view, projectCallback);
+ setupTabHost((TabHost) view, projectCallback);
} else if (view instanceof QuickContactBadge) {
QuickContactBadge badge = (QuickContactBadge) view;
badge.setImageToDefault();
@@ -1249,7 +1271,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
boolean skipCallbackParser = false;
int count = binding.getHeaderCount();
- for (int i = 0 ; i < count ; i++) {
+ for (int i = 0; i < count; i++) {
Pair<View, Boolean> pair = context.inflateView(
binding.getHeaderAt(i),
list, false /*attachToRoot*/, skipCallbackParser);
@@ -1261,7 +1283,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
count = binding.getFooterCount();
- for (int i = 0 ; i < count ; i++) {
+ for (int i = 0; i < count; i++) {
Pair<View, Boolean> pair = context.inflateView(
binding.getFooterAt(i),
list, false /*attachToRoot*/, skipCallbackParser);
@@ -1290,11 +1312,11 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
}
} else if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup)view;
+ ViewGroup group = (ViewGroup) view;
final int count = group.getChildCount();
- for (int c = 0 ; c < count ; c++) {
+ for (int c = 0; c < count; c++) {
View child = group.getChildAt(c);
- postInflateProcess(child, projectCallback);
+ postInflateProcess(child, projectCallback, skip);
}
}
}
@@ -1362,6 +1384,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
for (int i = 0 ; i < count ; i++) {
View child = content.getChildAt(i);
String tabSpec = String.format("tab_spec%d", i+1);
+ @SuppressWarnings("ConstantConditions") // child cannot be null.
int id = child.getId();
@SuppressWarnings("deprecation")
Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id);
@@ -1624,11 +1647,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
/**
* Creates the action bar. Also queries the project callback for missing information.
*/
- private ActionBarLayout createActionBar(BridgeContext context, SessionParams params) {
- ActionBarLayout actionBar = new ActionBarLayout(context, params);
- actionBar.setLayoutParams(new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- return actionBar;
+ private ActionBarLayout createActionBar(BridgeContext context, SessionParams params,
+ ViewGroup parentView) {
+ return new ActionBarLayout(context, params, parentView);
}
public BufferedImage getImage() {
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 22f8e1c..677c744 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
@@ -32,6 +32,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.ColorStateList;
+import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
import android.graphics.NinePatch_Delegate;
@@ -166,6 +167,17 @@ public final class ResourceHelper {
* @param context the current context
*/
public static Drawable getDrawable(ResourceValue value, BridgeContext context) {
+ return getDrawable(value, context, null);
+ }
+
+ /**
+ * Returns a drawable from the given value.
+ * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable,
+ * or an hexadecimal color
+ * @param context the current context
+ * @param theme the theme to be used to inflate the drawable.
+ */
+ public static Drawable getDrawable(ResourceValue value, BridgeContext context, Theme theme) {
if (value == null) {
return null;
}
@@ -209,7 +221,7 @@ public final class ResourceHelper {
BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
parser, context, value.isFramework());
try {
- return Drawable.createFromXml(context.getResources(), blockParser);
+ return Drawable.createFromXml(context.getResources(), blockParser, theme);
} finally {
blockParser.ensurePopped();
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java
new file mode 100644
index 0000000..7e361a1
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.libcore.io;
+
+import java.nio.ByteBuffer;
+
+import libcore.io.BufferIterator;
+
+/**
+ * Provides an implementation of {@link BufferIterator} over a {@link ByteBuffer}.
+ */
+public class BridgeBufferIterator extends BufferIterator {
+
+ private final long mSize;
+ private final ByteBuffer mByteBuffer;
+
+ public BridgeBufferIterator(long size, ByteBuffer buffer) {
+ mSize = size;
+ mByteBuffer = buffer;
+ }
+
+ @Override
+ public void seek(int offset) {
+ assert offset <= mSize;
+ mByteBuffer.position(offset);
+ }
+
+ @Override
+ public void skip(int byteCount) {
+ int newPosition = mByteBuffer.position() + byteCount;
+ assert newPosition <= mSize;
+ mByteBuffer.position(newPosition);
+ }
+
+ @Override
+ public void readByteArray(byte[] dst, int dstOffset, int byteCount) {
+ assert dst.length >= dstOffset + byteCount;
+ mByteBuffer.get(dst, dstOffset, byteCount);
+ }
+
+ @Override
+ public byte readByte() {
+ return mByteBuffer.get();
+ }
+
+ @Override
+ public int readInt() {
+ return mByteBuffer.getInt();
+ }
+
+ @Override
+ public void readIntArray(int[] dst, int dstOffset, int intCount) {
+ while (--intCount >= 0) {
+ dst[dstOffset++] = mByteBuffer.getInt();
+ }
+ }
+
+ @Override
+ public short readShort() {
+ return mByteBuffer.getShort();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
index a1fae95..979aa33 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
@@ -16,6 +16,7 @@
package com.android.layoutlib.bridge.util;
+import com.android.annotations.NonNull;
import com.android.resources.ResourceType;
import com.android.util.Pair;
@@ -48,6 +49,7 @@ public class DynamicIdMap {
* @param name the name of the resource
* @return an integer.
*/
+ @NonNull
public Integer getId(ResourceType type, String name) {
return getId(Pair.of(type, name));
}
@@ -59,10 +61,11 @@ public class DynamicIdMap {
* @param resource the type/name of the resource
* @return an integer.
*/
+ @NonNull
public Integer getId(Pair<ResourceType, String> resource) {
Integer value = mDynamicIds.get(resource);
if (value == null) {
- value = Integer.valueOf(++mDynamicSeed);
+ value = ++mDynamicSeed;
mDynamicIds.put(resource, value);
mRevDynamicIds.put(value, resource);
}
diff --git a/tools/layoutlib/bridge/src/libcore/io/MemoryMappedFile_Delegate.java b/tools/layoutlib/bridge/src/libcore/io/MemoryMappedFile_Delegate.java
new file mode 100644
index 0000000..723d5c4
--- /dev/null
+++ b/tools/layoutlib/bridge/src/libcore/io/MemoryMappedFile_Delegate.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.io;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.libcore.io.BridgeBufferIterator;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.system.ErrnoException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel.MapMode;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Delegate used to provide alternate implementation of select methods of {@link MemoryMappedFile}.
+ */
+public class MemoryMappedFile_Delegate {
+
+ private static final DelegateManager<MemoryMappedFile_Delegate> sManager = new
+ DelegateManager<MemoryMappedFile_Delegate>(MemoryMappedFile_Delegate.class);
+
+ private static final Map<MemoryMappedFile, Long> sMemoryMappedFileMap =
+ new HashMap<MemoryMappedFile, Long>();
+
+ private final MappedByteBuffer mMappedByteBuffer;
+ private final long mSize;
+
+ /** Path on the target device where the data file is available. */
+ private static final String TARGET_PATH = System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo";
+ /** Path on the host (inside the SDK) where the data files are available. */
+ private static File sRootPath;
+
+ @LayoutlibDelegate
+ static MemoryMappedFile mmapRO(String path) throws ErrnoException {
+ if (!path.startsWith(TARGET_PATH)) {
+ throw new ErrnoException("Custom timezone data files are not supported.", 1);
+ }
+ if (sRootPath == null) {
+ throw new ErrnoException("Bridge has not been initialized properly.", 1);
+ }
+ path = path.substring(TARGET_PATH.length());
+ try {
+ File f = new File(sRootPath, path);
+ if (!f.exists()) {
+ throw new ErrnoException("File not found: " + f.getPath(), 1);
+ }
+ RandomAccessFile file = new RandomAccessFile(f, "r");
+ try {
+ long size = file.length();
+ MemoryMappedFile_Delegate newDelegate = new MemoryMappedFile_Delegate(file);
+ long filePointer = file.getFilePointer();
+ MemoryMappedFile mmFile = new MemoryMappedFile(filePointer, size);
+ long delegateIndex = sManager.addNewDelegate(newDelegate);
+ sMemoryMappedFileMap.put(mmFile, delegateIndex);
+ return mmFile;
+ } finally {
+ file.close();
+ }
+ } catch (IOException e) {
+ throw new ErrnoException("mmapRO", 1, e);
+ }
+ }
+
+ @LayoutlibDelegate
+ static void close(MemoryMappedFile thisFile) throws ErrnoException {
+ Long index = sMemoryMappedFileMap.get(thisFile);
+ if (index != null) {
+ sMemoryMappedFileMap.remove(thisFile);
+ sManager.removeJavaReferenceFor(index);
+ }
+ }
+
+ @LayoutlibDelegate
+ static BufferIterator bigEndianIterator(MemoryMappedFile file) {
+ MemoryMappedFile_Delegate delegate = getDelegate(file);
+ return new BridgeBufferIterator(delegate.mSize, delegate.mMappedByteBuffer.duplicate());
+ }
+
+ // TODO: implement littleEndianIterator()
+
+ public MemoryMappedFile_Delegate(RandomAccessFile file) throws IOException {
+ mSize = file.length();
+ // It's weird that map() takes size as long, but returns MappedByteBuffer which uses an int
+ // to store the marker to the position.
+ mMappedByteBuffer = file.getChannel().map(MapMode.READ_ONLY, 0, mSize);
+ assert mMappedByteBuffer.order() == ByteOrder.BIG_ENDIAN;
+ }
+
+ public static void setDataDir(File path) {
+ sRootPath = path;
+ }
+
+ private static MemoryMappedFile_Delegate getDelegate(MemoryMappedFile file) {
+ Long index = sMemoryMappedFileMap.get(file);
+ return index == null ? null : sManager.getDelegate(index);
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java b/tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java
index adad2ac..f29c5c0 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
+++ b/tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,19 @@
* limitations under the License.
*/
-package android.graphics;
+package libcore.util;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.util.GregorianCalendar;
/**
- * Class allowing access to package-protected methods/fields.
+ * Delegate used to provide alternate implementation of select methods in {@link ZoneInfo.WallTime}
*/
-public class Typeface_Accessor {
+public class ZoneInfo_WallTime_Delegate {
- public static void resetDefaults() {
- Typeface.sDefaults = null;
+ @LayoutlibDelegate
+ static GregorianCalendar createGregorianCalendar() {
+ return new GregorianCalendar();
}
}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
new file mode 100644
index 0000000..943cdf1
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml
index 2704c07..b8ec5661 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml
@@ -8,4 +8,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Some text"/>
+ <DatePicker
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
+ <CalendarView
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
</LinearLayout> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
new file mode 100644
index 0000000..e13ad72
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.intensive;
+
+import com.android.annotations.NonNull;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.ImageIO;
+
+import static java.awt.RenderingHints.*;
+import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
+import static java.io.File.separatorChar;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+// Adapted by taking the relevant pieces of code from the following classes:
+//
+// com.android.tools.idea.rendering.ImageUtils,
+// com.android.tools.idea.tests.gui.framework.fixture.layout.ImageFixture and
+// com.android.tools.idea.rendering.RenderTestBase
+/**
+ * Utilities related to image processing.
+ */
+public class ImageUtils {
+ /**
+ * Normally, this test will fail when there is a missing thumbnail. However, when
+ * you create creating a new test, it's useful to be able to turn this off such that
+ * you can generate all the missing thumbnails in one go, rather than having to run
+ * the test repeatedly to get to each new render assertion generating its thumbnail.
+ */
+ private static final boolean FAIL_ON_MISSING_THUMBNAIL = true;
+
+ private static final int THUMBNAIL_SIZE = 250;
+
+ private static final double MAX_PERCENT_DIFFERENCE = 0.1;
+
+ public static void requireSimilar(@NonNull String relativePath, @NonNull BufferedImage image)
+ throws IOException {
+ int maxDimension = Math.max(image.getWidth(), image.getHeight());
+ double scale = THUMBNAIL_SIZE / (double)maxDimension;
+ BufferedImage thumbnail = scale(image, scale, scale);
+
+ InputStream is = ImageUtils.class.getResourceAsStream(relativePath);
+ if (is == null) {
+ String message = "Unable to load golden thumbnail: " + relativePath + "\n";
+ message = saveImageAndAppendMessage(thumbnail, message, relativePath);
+ if (FAIL_ON_MISSING_THUMBNAIL) {
+ fail(message);
+ } else {
+ System.out.println(message);
+ }
+ }
+ else {
+ BufferedImage goldenImage = ImageIO.read(is);
+ assertImageSimilar(relativePath, goldenImage, thumbnail, MAX_PERCENT_DIFFERENCE);
+ }
+ }
+
+ public static void assertImageSimilar(String relativePath, BufferedImage goldenImage,
+ BufferedImage image, double maxPercentDifferent) throws IOException {
+ assertEquals("Only TYPE_INT_ARGB image types are supported", TYPE_INT_ARGB, image.getType());
+
+ if (goldenImage.getType() != TYPE_INT_ARGB) {
+ BufferedImage temp = new BufferedImage(goldenImage.getWidth(), goldenImage.getHeight(),
+ TYPE_INT_ARGB);
+ temp.getGraphics().drawImage(goldenImage, 0, 0, null);
+ goldenImage = temp;
+ }
+ assertEquals(TYPE_INT_ARGB, goldenImage.getType());
+
+ int imageWidth = Math.min(goldenImage.getWidth(), image.getWidth());
+ int imageHeight = Math.min(goldenImage.getHeight(), image.getHeight());
+
+ // Blur the images to account for the scenarios where there are pixel
+ // differences
+ // in where a sharp edge occurs
+ // goldenImage = blur(goldenImage, 6);
+ // image = blur(image, 6);
+
+ int width = 3 * imageWidth;
+ @SuppressWarnings("UnnecessaryLocalVariable")
+ int height = imageHeight; // makes code more readable
+ BufferedImage deltaImage = new BufferedImage(width, height, TYPE_INT_ARGB);
+ Graphics g = deltaImage.getGraphics();
+
+ // Compute delta map
+ long delta = 0;
+ for (int y = 0; y < imageHeight; y++) {
+ for (int x = 0; x < imageWidth; x++) {
+ int goldenRgb = goldenImage.getRGB(x, y);
+ int rgb = image.getRGB(x, y);
+ if (goldenRgb == rgb) {
+ deltaImage.setRGB(imageWidth + x, y, 0x00808080);
+ continue;
+ }
+
+ // If the pixels have no opacity, don't delta colors at all
+ if (((goldenRgb & 0xFF000000) == 0) && (rgb & 0xFF000000) == 0) {
+ deltaImage.setRGB(imageWidth + x, y, 0x00808080);
+ continue;
+ }
+
+ int deltaR = ((rgb & 0xFF0000) >>> 16) - ((goldenRgb & 0xFF0000) >>> 16);
+ int newR = 128 + deltaR & 0xFF;
+ int deltaG = ((rgb & 0x00FF00) >>> 8) - ((goldenRgb & 0x00FF00) >>> 8);
+ int newG = 128 + deltaG & 0xFF;
+ int deltaB = (rgb & 0x0000FF) - (goldenRgb & 0x0000FF);
+ int newB = 128 + deltaB & 0xFF;
+
+ int avgAlpha = ((((goldenRgb & 0xFF000000) >>> 24)
+ + ((rgb & 0xFF000000) >>> 24)) / 2) << 24;
+
+ int newRGB = avgAlpha | newR << 16 | newG << 8 | newB;
+ deltaImage.setRGB(imageWidth + x, y, newRGB);
+
+ delta += Math.abs(deltaR);
+ delta += Math.abs(deltaG);
+ delta += Math.abs(deltaB);
+ }
+ }
+
+ // 3 different colors, 256 color levels
+ long total = imageHeight * imageWidth * 3L * 256L;
+ float percentDifference = (float) (delta * 100 / (double) total);
+
+ String error = null;
+ String imageName = getName(relativePath);
+ if (percentDifference > maxPercentDifferent) {
+ error = String.format("Images differ (by %.1f%%)", percentDifference);
+ } else if (Math.abs(goldenImage.getWidth() - image.getWidth()) >= 2) {
+ error = "Widths differ too much for " + imageName + ": " +
+ goldenImage.getWidth() + "x" + goldenImage.getHeight() +
+ "vs" + image.getWidth() + "x" + image.getHeight();
+ } else if (Math.abs(goldenImage.getHeight() - image.getHeight()) >= 2) {
+ error = "Heights differ too much for " + imageName + ": " +
+ goldenImage.getWidth() + "x" + goldenImage.getHeight() +
+ "vs" + image.getWidth() + "x" + image.getHeight();
+ }
+
+ assertEquals(TYPE_INT_ARGB, image.getType());
+ if (error != null) {
+ // Expected on the left
+ // Golden on the right
+ g.drawImage(goldenImage, 0, 0, null);
+ g.drawImage(image, 2 * imageWidth, 0, null);
+
+ // Labels
+ if (imageWidth > 80) {
+ g.setColor(Color.RED);
+ g.drawString("Expected", 10, 20);
+ g.drawString("Actual", 2 * imageWidth + 10, 20);
+ }
+
+ File output = new File(getTempDir(), "delta-" + imageName);
+ if (output.exists()) {
+ boolean deleted = output.delete();
+ assertTrue(deleted);
+ }
+ ImageIO.write(deltaImage, "PNG", output);
+ error += " - see details in " + output.getPath() + "\n";
+ error = saveImageAndAppendMessage(image, error, relativePath);
+ System.out.println(error);
+ fail(error);
+ }
+
+ g.dispose();
+ }
+
+ /**
+ * Resize the given image
+ *
+ * @param source the image to be scaled
+ * @param xScale x scale
+ * @param yScale y scale
+ * @return the scaled image
+ */
+ @NonNull
+ public static BufferedImage scale(@NonNull BufferedImage source, double xScale, double yScale) {
+
+ int sourceWidth = source.getWidth();
+ int sourceHeight = source.getHeight();
+ int destWidth = Math.max(1, (int) (xScale * sourceWidth));
+ int destHeight = Math.max(1, (int) (yScale * sourceHeight));
+ int imageType = source.getType();
+ if (imageType == BufferedImage.TYPE_CUSTOM) {
+ imageType = BufferedImage.TYPE_INT_ARGB;
+ }
+ if (xScale > 0.5 && yScale > 0.5) {
+ BufferedImage scaled =
+ new BufferedImage(destWidth, destHeight, imageType);
+ Graphics2D g2 = scaled.createGraphics();
+ g2.setComposite(AlphaComposite.Src);
+ g2.setColor(new Color(0, true));
+ g2.fillRect(0, 0, destWidth, destHeight);
+ if (xScale == 1 && yScale == 1) {
+ g2.drawImage(source, 0, 0, null);
+ } else {
+ setRenderingHints(g2);
+ g2.drawImage(source, 0, 0, destWidth, destHeight, 0, 0, sourceWidth, sourceHeight,
+ null);
+ }
+ g2.dispose();
+ return scaled;
+ } else {
+ // When creating a thumbnail, using the above code doesn't work very well;
+ // you get some visible artifacts, especially for text. Instead use the
+ // technique of repeatedly scaling the image into half; this will cause
+ // proper averaging of neighboring pixels, and will typically (for the kinds
+ // of screen sizes used by this utility method in the layout editor) take
+ // about 3-4 iterations to get the result since we are logarithmically reducing
+ // the size. Besides, each successive pass in operating on much fewer pixels
+ // (a reduction of 4 in each pass).
+ //
+ // However, we may not be resizing to a size that can be reached exactly by
+ // successively diving in half. Therefore, once we're within a factor of 2 of
+ // the final size, we can do a resize to the exact target size.
+ // However, we can get even better results if we perform this final resize
+ // up front. Let's say we're going from width 1000 to a destination width of 85.
+ // The first approach would cause a resize from 1000 to 500 to 250 to 125, and
+ // then a resize from 125 to 85. That last resize can distort/blur a lot.
+ // Instead, we can start with the destination width, 85, and double it
+ // successfully until we're close to the initial size: 85, then 170,
+ // then 340, and finally 680. (The next one, 1360, is larger than 1000).
+ // So, now we *start* the thumbnail operation by resizing from width 1000 to
+ // width 680, which will preserve a lot of visual details such as text.
+ // Then we can successively resize the image in half, 680 to 340 to 170 to 85.
+ // We end up with the expected final size, but we've been doing an exact
+ // divide-in-half resizing operation at the end so there is less distortion.
+
+ int iterations = 0; // Number of halving operations to perform after the initial resize
+ int nearestWidth = destWidth; // Width closest to source width that = 2^x, x is integer
+ int nearestHeight = destHeight;
+ while (nearestWidth < sourceWidth / 2) {
+ nearestWidth *= 2;
+ nearestHeight *= 2;
+ iterations++;
+ }
+
+ BufferedImage scaled = new BufferedImage(nearestWidth, nearestHeight, imageType);
+
+ Graphics2D g2 = scaled.createGraphics();
+ setRenderingHints(g2);
+ g2.drawImage(source, 0, 0, nearestWidth, nearestHeight, 0, 0, sourceWidth, sourceHeight,
+ null);
+ g2.dispose();
+
+ sourceWidth = nearestWidth;
+ sourceHeight = nearestHeight;
+ source = scaled;
+
+ for (int iteration = iterations - 1; iteration >= 0; iteration--) {
+ int halfWidth = sourceWidth / 2;
+ int halfHeight = sourceHeight / 2;
+ scaled = new BufferedImage(halfWidth, halfHeight, imageType);
+ g2 = scaled.createGraphics();
+ setRenderingHints(g2);
+ g2.drawImage(source, 0, 0, halfWidth, halfHeight, 0, 0, sourceWidth, sourceHeight,
+ null);
+ g2.dispose();
+
+ sourceWidth = halfWidth;
+ sourceHeight = halfHeight;
+ source = scaled;
+ iterations--;
+ }
+ return scaled;
+ }
+ }
+
+ private static void setRenderingHints(@NonNull Graphics2D g2) {
+ g2.setRenderingHint(KEY_INTERPOLATION,VALUE_INTERPOLATION_BILINEAR);
+ g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
+ g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
+ }
+
+ /**
+ * Temp directory where to write the thumbnails and deltas.
+ */
+ @NonNull
+ private static File getTempDir() {
+ if (System.getProperty("os.name").equals("Mac OS X")) {
+ return new File("/tmp"); //$NON-NLS-1$
+ }
+
+ return new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
+ }
+
+ /**
+ * Saves the generated thumbnail image and appends the info message to an initial message
+ */
+ @NonNull
+ private static String saveImageAndAppendMessage(@NonNull BufferedImage image,
+ @NonNull String initialMessage, @NonNull String relativePath) throws IOException {
+ File output = new File(getTempDir(), getName(relativePath));
+ if (output.exists()) {
+ boolean deleted = output.delete();
+ assertTrue(deleted);
+ }
+ ImageIO.write(image, "PNG", output);
+ initialMessage += "Thumbnail for current rendering stored at " + output.getPath();
+// initialMessage += "\nRun the following command to accept the changes:\n";
+// initialMessage += String.format("mv %1$s %2$s", output.getPath(),
+// ImageUtils.class.getResource(relativePath).getPath());
+ // The above has been commented out, since the destination path returned is in out dir
+ // and it makes the tests pass without the code being actually checked in.
+ return initialMessage;
+ }
+
+ private static String getName(@NonNull String relativePath) {
+ return relativePath.substring(relativePath.lastIndexOf(separatorChar) + 1);
+ }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index a2588a6..a86fcdd 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -39,6 +39,7 @@ import org.junit.Test;
import java.io.File;
import java.io.FileFilter;
+import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Comparator;
@@ -75,7 +76,10 @@ public class Main {
private static final String PLATFORM_DIR;
private static final String TEST_RES_DIR;
- private static final String APP_TEST_RES = "/testApp/MyApplication/src/main/res";
+ /** Location of the app to test inside {@link #TEST_RES_DIR}*/
+ private static final String APP_TEST_DIR = "/testApp/MyApplication";
+ /** Location of the app's res dir inside {@link #TEST_RES_DIR}*/
+ private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
private LayoutLog mLayoutLibLog;
private FrameworkResources mFrameworkRepo;
@@ -161,11 +165,27 @@ public class Main {
if (!out.isDirectory()) {
return null;
}
- File sdkDir = new File(out, "sdk" + File.separator + "sdk");
+ File sdkDir = new File(out, "sdk");
if (!sdkDir.isDirectory()) {
- // The directory we thought that should contain the sdk is not a directory.
return null;
}
+ File[] sdkDirs = sdkDir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File path) {
+ // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
+ return path.isDirectory() && path.getName().startsWith("sdk");
+ }
+ });
+ for (File dir : sdkDirs) {
+ String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
+ if (platformDir != null) {
+ return platformDir;
+ }
+ }
+ return null;
+ }
+
+ private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
File[] possibleSdks = sdkDir.listFiles(new FileFilter() {
@Override
public boolean accept(File path) {
@@ -280,6 +300,12 @@ public class Main {
getLogger().error(session.getResult().getException(),
session.getResult().getErrorMessage());
}
+ try {
+ String goldenImagePath = APP_TEST_DIR + "/golden/activity.png";
+ ImageUtils.requireSimilar(goldenImagePath, session.getImage());
+ } catch (IOException e) {
+ getLogger().error(e, e.getMessage());
+ }
}
/**
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 83fac85..8f50c5d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -20,7 +20,9 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import com.android.tools.layoutlib.java.AutoCloseable;
import com.android.tools.layoutlib.java.Charsets;
import com.android.tools.layoutlib.java.IntegralToString;
+import com.android.tools.layoutlib.java.LinkedHashMap_Delegate;
import com.android.tools.layoutlib.java.Objects;
+import com.android.tools.layoutlib.java.System_Delegate;
import com.android.tools.layoutlib.java.UnsafeByteSequence;
import java.util.Arrays;
@@ -131,25 +133,28 @@ public final class CreateInfo implements ICreateInfo {
IntegralToString.class,
UnsafeByteSequence.class,
Charsets.class,
+ System_Delegate.class,
+ LinkedHashMap_Delegate.class,
};
/**
* The list of methods to rewrite as delegates.
*/
public final static String[] DELEGATE_METHODS = new String[] {
+ "android.animation.AnimatorInflater#loadAnimator", // TODO: remove when Path.approximate() is supported.
"android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
"android.content.res.Resources$Theme#obtainStyledAttributes",
"android.content.res.Resources$Theme#resolveAttribute",
"android.content.res.Resources$Theme#resolveAttributes",
"android.content.res.AssetManager#newTheme",
"android.content.res.AssetManager#deleteTheme",
- "android.content.res.AssetManager#applyThemeStyle",
"android.content.res.TypedArray#getValueAt",
"android.content.res.TypedArray#obtain",
"android.graphics.BitmapFactory#finishDecode",
"android.graphics.Typeface#getSystemFontConfigLocation",
"android.os.Handler#sendMessageAtTime",
"android.os.HandlerThread#run",
+ "android.preference.Preference#getView",
"android.text.format.DateFormat#is24HourFormat",
"android.util.Xml#newPullParser",
"android.view.Choreographer#getRefreshRate",
@@ -162,10 +167,20 @@ public final class CreateInfo implements ICreateInfo {
"android.view.WindowManagerGlobal#getWindowManagerService",
"android.view.inputmethod.InputMethodManager#getInstance",
"android.view.MenuInflater#registerMenu",
+ "android.view.RenderNode#nCreate",
+ "android.view.RenderNode#nDestroyRenderNode",
+ "android.view.RenderNode#nSetElevation",
+ "android.view.RenderNode#nGetElevation",
+ "android.view.ViewGroup#drawChild",
+ "android.widget.TimePickerClockDelegate#getAmOrPmKeyCode",
"com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
"com.android.internal.util.XmlUtils#convertValueToInt",
"com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
- "dalvik.system.VMRuntime#newUnpaddedArray"
+ "dalvik.system.VMRuntime#newUnpaddedArray",
+ "libcore.io.MemoryMappedFile#mmapRO",
+ "libcore.io.MemoryMappedFile#close",
+ "libcore.io.MemoryMappedFile#bigEndianIterator",
+ "libcore.util.ZoneInfo$WallTime#createGregorianCalendar",
};
/**
@@ -215,7 +230,6 @@ public final class CreateInfo implements ICreateInfo {
"android.os.SystemProperties",
"android.text.AndroidBidi",
"android.text.StaticLayout",
- "android.text.format.Time",
"android.view.Display",
"libcore.icu.DateIntervalFormat",
"libcore.icu.ICU",
@@ -255,10 +269,12 @@ public final class CreateInfo implements ICreateInfo {
"java.nio.charset.Charsets", "com.android.tools.layoutlib.java.Charsets",
"java.lang.IntegralToString", "com.android.tools.layoutlib.java.IntegralToString",
"java.lang.UnsafeByteSequence", "com.android.tools.layoutlib.java.UnsafeByteSequence",
+ "java.nio.charset.StandardCharsets", "com.android.tools.layoutlib.java.Charsets",
};
private final static String[] EXCLUDED_CLASSES =
new String[] {
+ "android.preference.PreferenceActivity",
"org.kxml2.io.KXmlParser"
};
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index 3d89c68..ae4a57d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -112,6 +112,7 @@ public class DelegateClassAdapter extends ClassVisitor {
// The implementation of this 'delegate' method is done in layoutlib_bridge.
int accessDelegate = access;
+ access = access & ~Opcodes.ACC_PRIVATE; // If private, make it package protected.
MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX,
desc, signature, exceptions);
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index cd3c39e..fa570c8 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -108,6 +108,7 @@ public class Main {
"android.graphics.drawable.*",
"android.content.*",
"android.content.res.*",
+ "android.preference.*",
"org.apache.harmony.xml.*",
"com.android.internal.R**",
"android.pim.*", // for datepicker
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index 9c6fbac..384d8ca 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -16,6 +16,9 @@
package com.android.tools.layoutlib.create;
+import com.android.tools.layoutlib.java.LinkedHashMap_Delegate;
+import com.android.tools.layoutlib.java.System_Delegate;
+
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -24,8 +27,10 @@ import org.objectweb.asm.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Set;
/**
@@ -42,36 +47,38 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
"([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
"([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
- private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(2);
+ private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(5);
private static final String ANDROID_LOCALE_CLASS =
"com/android/layoutlib/bridge/android/AndroidLocale";
- private static final String JAVA_LOCALE_CLASS = "java/util/Locale";
+ private static final String JAVA_LOCALE_CLASS = Type.getInternalName(java.util.Locale.class);
private static final Type STRING = Type.getType(String.class);
+ private static final String JAVA_LANG_SYSTEM = Type.getInternalName(System.class);
+
// Static initialization block to initialize METHOD_REPLACERS.
static {
// Case 1: java.lang.System.arraycopy()
METHOD_REPLACERS.add(new MethodReplacer() {
@Override
public boolean isNeeded(String owner, String name, String desc) {
- return "java/lang/System".equals(owner) && "arraycopy".equals(name) &&
+ return JAVA_LANG_SYSTEM.equals(owner) && "arraycopy".equals(name) &&
ARRAYCOPY_DESCRIPTORS.contains(desc);
}
@Override
- public void replace(int[] opcode, String[] methodInformation) {
- assert methodInformation.length == 3 && isNeeded(methodInformation[0], methodInformation[1], methodInformation[2])
- && opcode.length == 1;
- methodInformation[2] = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
+ public void replace(MethodInformation mi) {
+ assert isNeeded(mi.owner, mi.name, mi.desc);
+ mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
}
});
// Case 2: java.util.Locale.toLanguageTag() and java.util.Locale.getScript()
METHOD_REPLACERS.add(new MethodReplacer() {
- String LOCALE_TO_STRING = Type.getMethodDescriptor(STRING, Type.getType(Locale.class));
+ private final String LOCALE_TO_STRING =
+ Type.getMethodDescriptor(STRING, Type.getType(Locale.class));
@Override
public boolean isNeeded(String owner, String name, String desc) {
@@ -80,12 +87,11 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
}
@Override
- public void replace(int[] opcode, String[] methodInformation) {
- assert methodInformation.length == 3 && isNeeded(methodInformation[0], methodInformation[1], methodInformation[2])
- && opcode.length == 1;
- opcode[0] = Opcodes.INVOKESTATIC;
- methodInformation[0] = ANDROID_LOCALE_CLASS;
- methodInformation[2] = LOCALE_TO_STRING;
+ public void replace(MethodInformation mi) {
+ assert isNeeded(mi.owner, mi.name, mi.desc);
+ mi.opcode = Opcodes.INVOKESTATIC;
+ mi.owner = ANDROID_LOCALE_CLASS;
+ mi.desc = LOCALE_TO_STRING;
}
});
@@ -104,10 +110,51 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
}
@Override
- public void replace(int[] opcode, String[] methodInformation) {
- assert methodInformation.length == 3 && isNeeded(methodInformation[0], methodInformation[1], methodInformation[2])
- && opcode.length == 1;
- methodInformation[0] = ANDROID_LOCALE_CLASS;
+ public void replace(MethodInformation mi) {
+ assert isNeeded(mi.owner, mi.name, mi.desc);
+ mi.owner = ANDROID_LOCALE_CLASS;
+ }
+ });
+
+ // Case 4: java.lang.System.log?()
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ @Override
+ public boolean isNeeded(String owner, String name, String desc) {
+ return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4
+ && name.startsWith("log");
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ assert isNeeded(mi.owner, mi.name, mi.desc);
+ assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V")
+ || mi.desc.equals("(Ljava/lang/String;)V");
+ mi.name = "log";
+ mi.owner = Type.getInternalName(System_Delegate.class);
+ }
+ });
+
+ // Case 5: java.util.LinkedHashMap.eldest()
+ METHOD_REPLACERS.add(new MethodReplacer() {
+
+ private final String VOID_TO_MAP_ENTRY =
+ Type.getMethodDescriptor(Type.getType(Map.Entry.class));
+ private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class);
+
+ @Override
+ public boolean isNeeded(String owner, String name, String desc) {
+ return LINKED_HASH_MAP.equals(owner) &&
+ "eldest".equals(name) &&
+ VOID_TO_MAP_ENTRY.equals(desc);
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ assert isNeeded(mi.owner, mi.name, mi.desc);
+ mi.opcode = Opcodes.INVOKESTATIC;
+ mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class);
+ mi.desc = Type.getMethodDescriptor(
+ Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class));
}
});
}
@@ -141,13 +188,12 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
for (MethodReplacer replacer : METHOD_REPLACERS) {
if (replacer.isNeeded(owner, name, desc)) {
- String[] methodInformation = {owner, name, desc};
- int[] opcodeOut = {opcode};
- replacer.replace(opcodeOut, methodInformation);
- opcode = opcodeOut[0];
- owner = methodInformation[0];
- name = methodInformation[1];
- desc = methodInformation[2];
+ MethodInformation mi = new MethodInformation(opcode, owner, name, desc);
+ replacer.replace(mi);
+ opcode = mi.opcode;
+ owner = mi.owner;
+ name = mi.name;
+ desc = mi.desc;
break;
}
}
@@ -155,19 +201,28 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
}
}
+ private static class MethodInformation {
+ public int opcode;
+ public String owner;
+ public String name;
+ public String desc;
+
+ public MethodInformation(int opcode, String owner, String name, String desc) {
+ this.opcode = opcode;
+ this.owner = owner;
+ this.name = name;
+ this.desc = desc;
+ }
+ }
+
private interface MethodReplacer {
public boolean isNeeded(String owner, String name, String desc);
/**
- * This method must update the arrays with the new values of the method attributes -
+ * Updates the MethodInformation with the new values of the method attributes -
* opcode, owner, name and desc.
- * @param opcode This array should contain the original value of the opcode. The value is
- * modified by the method if needed. The size of the array must be 1.
*
- * @param methodInformation This array should contain the original values of the method
- * attributes - owner, name and desc in that order. The values
- * may be modified as needed. The size of the array must be 3.
*/
- public void replace(int[] opcode, String[] methodInformation);
+ public void replace(MethodInformation mi);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/LinkedHashMap_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/LinkedHashMap_Delegate.java
new file mode 100644
index 0000000..59cc75f
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/LinkedHashMap_Delegate.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.java;
+
+import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Provides alternate implementation to java.util.LinkedHashMap#eldest(), which is present as a
+ * non-public method in the Android VM, but not present on the host VM. This is injected in the
+ * layoutlib using {@link ReplaceMethodCallsAdapter}.
+ */
+public class LinkedHashMap_Delegate {
+ public static <K,V> Map.Entry<K,V> eldest(LinkedHashMap<K,V> map) {
+ Iterator<Entry<K, V>> iterator = map.entrySet().iterator();
+ return iterator.hasNext() ? iterator.next() : null;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
new file mode 100644
index 0000000..613c8d9
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.java;
+
+import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter;
+
+/**
+ * Provides dummy implementation of methods that don't exist on the host VM.
+ *
+ * @see ReplaceMethodCallsAdapter
+ */
+public class System_Delegate {
+ public static void log(String message) {
+ // ignore.
+ }
+
+ public static void log(String message, Throwable th) {
+ // ignore.
+ }
+}