am 42556c39: am 3990ec34: am cdb87d8d: am a59c27dd: Fix ClassCastException when rendering ListView [DO NOT MERGE]
* commit '42556c394b1f7bf53f59aa6905dc0d20fe495389':
Fix ClassCastException when rendering ListView [DO NOT MERGE]
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index b316f23..60dddea 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1352,6 +1352,11 @@
private final AssetManager mAssets;
private final int mTheme;
+
+ // Needed by layoutlib.
+ /*package*/ int getNativeTheme() {
+ return mTheme;
+ }
}
/**
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index a7ee12b..71296fa 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -161,6 +161,7 @@
} else if (tagName.equals(XML_MENU)) {
// A menu start tag denotes a submenu for an item
SubMenu subMenu = menuState.addSubMenuItem();
+ registerMenu(subMenu, attrs);
// Parse the submenu into returned SubMenu
parseMenu(parser, attrs, subMenu);
@@ -183,9 +184,9 @@
if (!menuState.hasAddedItem()) {
if (menuState.itemActionProvider != null &&
menuState.itemActionProvider.hasSubMenu()) {
- menuState.addSubMenuItem();
+ registerMenu(menuState.addSubMenuItem(), attrs);
} else {
- menuState.addItem();
+ registerMenu(menuState.addItem(), attrs);
}
}
} else if (tagName.equals(XML_MENU)) {
@@ -200,7 +201,30 @@
eventType = parser.next();
}
}
-
+
+ /**
+ * The method is a hook for layoutlib to do its magic.
+ * Nothing is needed outside of LayoutLib. However, it should not be deleted because it
+ * appears to do nothing.
+ */
+ private void registerMenu(@SuppressWarnings("unused") MenuItem item,
+ @SuppressWarnings("unused") AttributeSet set) {
+ }
+
+ /**
+ * The method is a hook for layoutlib to do its magic.
+ * Nothing is needed outside of LayoutLib. However, it should not be deleted because it
+ * appears to do nothing.
+ */
+ private void registerMenu(@SuppressWarnings("unused") SubMenu subMenu,
+ @SuppressWarnings("unused") AttributeSet set) {
+ }
+
+ // Needed by layoutlib.
+ /*package*/ Context getContext() {
+ return mContext;
+ }
+
private static class InflatedOnMenuItemClickListener
implements MenuItem.OnMenuItemClickListener {
private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
@@ -446,9 +470,11 @@
}
}
- public void addItem() {
+ public MenuItem addItem() {
itemAdded = true;
- setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
+ MenuItem item = menu.add(groupId, itemId, itemCategoryOrder, itemTitle);
+ setItem(item);
+ return item;
}
public SubMenu addSubMenuItem() {
diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java
index 62afd2e..02b7030 100644
--- a/core/java/android/widget/ShareActionProvider.java
+++ b/core/java/android/widget/ShareActionProvider.java
@@ -161,9 +161,11 @@
@Override
public View onCreateActionView() {
// Create the view and set its data model.
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
ActivityChooserView activityChooserView = new ActivityChooserView(mContext);
- activityChooserView.setActivityChooserModel(dataModel);
+ if (!activityChooserView.isInEditMode()) {
+ ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
+ activityChooserView.setActivityChooserModel(dataModel);
+ }
// Lookup and set the expand action icon.
TypedValue outTypedValue = new TypedValue();
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index f041f07..6233522 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -162,6 +162,15 @@
init(dialog.getWindow().getDecorView());
}
+ /**
+ * Only for edit mode.
+ * @hide
+ */
+ public ActionBarImpl(View layout) {
+ assert layout.isInEditMode();
+ init(layout);
+ }
+
private void init(View decor) {
mContext = decor.getContext();
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
@@ -552,8 +561,8 @@
return;
}
- final FragmentTransaction trans = mActivity.getFragmentManager().beginTransaction()
- .disallowAddToBackStack();
+ final FragmentTransaction trans = mActionView.isInEditMode() ? null :
+ mActivity.getFragmentManager().beginTransaction().disallowAddToBackStack();
if (mSelectedTab == tab) {
if (mSelectedTab != null) {
@@ -571,7 +580,7 @@
}
}
- if (!trans.isEmpty()) {
+ if (trans != null && !trans.isEmpty()) {
trans.commit();
}
}
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 458ea2f..542e61e 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -386,8 +386,8 @@
private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
final int ordering = getOrdering(categoryOrder);
- final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder,
- ordering, title, mDefaultShowAsAction);
+ final MenuItemImpl item = createNewMenuItem(group, id, categoryOrder, ordering, title,
+ mDefaultShowAsAction);
if (mCurrentMenuInfo != null) {
// Pass along the current menu info
@@ -399,7 +399,14 @@
return item;
}
-
+
+ // Layoutlib overrides this method to return its custom implementation of MenuItemImpl
+ private MenuItemImpl createNewMenuItem(int group, int id, int categoryOrder, int ordering,
+ CharSequence title, int defaultShowAsAction) {
+ return new MenuItemImpl(this, group, id, categoryOrder, ordering, title,
+ defaultShowAsAction);
+ }
+
public MenuItem add(CharSequence title) {
return addInternal(0, 0, 0, title);
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 0f964b9..534e034 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -207,7 +207,7 @@
Log.e(TAG, "Activity component name not found!", e);
}
}
- if (mIcon == null) {
+ if (mIcon == null && !isInEditMode()) {
mIcon = appInfo.loadIcon(pm);
}
}
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index b27ce0e..c1c450b 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -31,6 +31,11 @@
built_core_dep := $(call java-lib-deps,core)
built_core_classes := $(call java-lib-files,core)
+built_ext_dep := $(call java-lib-deps,ext)
+built_ext_classes := $(call java-lib-files,ext)
+built_ext_data := $(call intermediates-dir-for, \
+ JAVA_LIBRARIES,ext,,COMMON)/javalib.jar
+
built_layoutlib_create_jar := $(call intermediates-dir-for, \
JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar
@@ -47,6 +52,8 @@
$(LOCAL_BUILT_MODULE): $(built_core_dep) \
$(built_framework_dep) \
+ $(built_ext_dep) \
+ $(built_ext_data) \
$(built_layoutlib_create_jar)
$(hide) echo "host layoutlib_create: $@"
$(hide) mkdir -p $(dir $@)
@@ -55,7 +62,9 @@
$(hide) java -jar $(built_layoutlib_create_jar) \
$@ \
$(built_core_classes) \
- $(built_framework_classes)
+ $(built_framework_classes) \
+ $(built_ext_classes) \
+ $(built_ext_data)
$(hide) ls -l $(built_framework_classes)
diff --git a/tools/layoutlib/bridge/.classpath b/tools/layoutlib/bridge/.classpath
index 3c124d9..2e4274d 100644
--- a/tools/layoutlib/bridge/.classpath
+++ b/tools/layoutlib/bridge/.classpath
@@ -7,5 +7,6 @@
<classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/>
<classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar"/>
<classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/tools-common/tools-common-prebuilt.jar"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/icu4j/icu4j.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk
index 687a91f..e3d48fc 100644
--- a/tools/layoutlib/bridge/Android.mk
+++ b/tools/layoutlib/bridge/Android.mk
@@ -22,6 +22,7 @@
LOCAL_JAVA_LIBRARIES := \
kxml2-2.3.0 \
+ icu4j \
layoutlib_api-prebuilt \
tools-common-prebuilt
diff --git a/tools/layoutlib/bridge/resources/bars/action_bar.xml b/tools/layoutlib/bridge/resources/bars/action_bar.xml
deleted file mode 100644
index 7adc5af..0000000
--- a/tools/layoutlib/bridge/resources/bars/action_bar.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <include layout="@android:layout/action_bar_home" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png
new file mode 100644
index 0000000..829378e
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png
index bd44b52..931daed 100644
--- a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..782ebfe
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..677b471
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..a1b8062
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..fcdbefe
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..633d864
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..4665e2a
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png
new file mode 100644
index 0000000..2773a70
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png
index c629387..6e1ac91 100644
--- a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/status_bar.xml b/tools/layoutlib/bridge/resources/bars/status_bar.xml
index d3c492e..51b474d 100644
--- a/tools/layoutlib/bridge/resources/bars/status_bar.xml
+++ b/tools/layoutlib/bridge/resources/bars/status_bar.xml
@@ -6,10 +6,12 @@
android:layout_weight="1"/>
<ImageView
android:layout_height="wrap_content"
- android:layout_width="wrap_content"/>
+ android:layout_width="wrap_content"
+ android:layout_marginTop="1dp"/>
<ImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:layout_marginLeft="3dip"
- android:layout_marginRight="5dip"/>
+ android:layout_marginLeft="3dp"
+ android:layout_marginRight="5dp"
+ android:layout_marginTop="1dp"/>
</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png
new file mode 100644
index 0000000..c7fd719
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png
new file mode 100644
index 0000000..625c61d
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png
Binary files differ
diff --git a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java
new file mode 100644
index 0000000..f3d6ba3
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java
@@ -0,0 +1,48 @@
+/*
+ * 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.content.res;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide implementation of a select few native methods of {@link AssetManager}
+ * <p/>
+ * Through the layoutlib_create tool, the original native methods of AssetManager have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class AssetManager_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static int newTheme(AssetManager manager) {
+ return Resources_Theme_Delegate.getDelegateManager()
+ .addNewDelegate(new Resources_Theme_Delegate());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void deleteTheme(AssetManager manager, int theme) {
+ Resources_Theme_Delegate.getDelegateManager().removeJavaReferenceFor(theme);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void applyThemeStyle(int theme, int styleRes, boolean force) {
+ Resources_Theme_Delegate delegate = Resources_Theme_Delegate.getDelegateManager()
+ .getDelegate(theme);
+ delegate.mThemeResId = styleRes;
+ delegate.force = force;
+ }
+}
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 c9d615c..b89d15f 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
@@ -16,7 +16,13 @@
package android.content.res;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.resources.ResourceType;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.content.res.Resources.NotFoundException;
@@ -25,7 +31,7 @@
import android.util.TypedValue;
/**
- * Delegate used to provide new implementation of a select few methods of {@link Resources$Theme}
+ * Delegate used to provide new implementation of a select few methods of {@link Resources.Theme}
*
* Through the layoutlib_create tool, the original methods of Theme have been replaced
* by calls to methods of the same name in this delegate class.
@@ -33,11 +39,30 @@
*/
public class Resources_Theme_Delegate {
+ // Resource identifier for the theme.
+ int mThemeResId;
+ // Whether to use the Theme.mThemeResId as primary theme.
+ boolean force;
+
+ // ---- delegate manager ----
+
+ private static final DelegateManager<Resources_Theme_Delegate> sManager =
+ new DelegateManager<Resources_Theme_Delegate>(Resources_Theme_Delegate.class);
+
+ public static DelegateManager<Resources_Theme_Delegate> getDelegateManager() {
+ return sManager;
+ }
+
+ // ---- delegate methods. ----
+
@LayoutlibDelegate
/*package*/ static TypedArray obtainStyledAttributes(
Resources thisResources, Theme thisTheme,
int[] attrs) {
- return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs);
+ boolean changed = setupResources(thisTheme);
+ TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs);
+ restoreResources(changed);
+ return ta;
}
@LayoutlibDelegate
@@ -45,15 +70,21 @@
Resources thisResources, Theme thisTheme,
int resid, int[] attrs)
throws NotFoundException {
- return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs);
+ boolean changed = setupResources(thisTheme);
+ TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs);
+ restoreResources(changed);
+ return ta;
}
@LayoutlibDelegate
/*package*/ static TypedArray obtainStyledAttributes(
Resources thisResources, Theme thisTheme,
AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
- return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(
- set, attrs, defStyleAttr, defStyleRes);
+ boolean changed = setupResources(thisTheme);
+ TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(set, attrs,
+ defStyleAttr, defStyleRes);
+ restoreResources(changed);
+ return ta;
}
@LayoutlibDelegate
@@ -61,7 +92,45 @@
Resources thisResources, Theme thisTheme,
int resid, TypedValue outValue,
boolean resolveRefs) {
- return RenderSessionImpl.getCurrentContext().resolveThemeAttribute(
+ boolean changed = setupResources(thisTheme);
+ boolean found = RenderSessionImpl.getCurrentContext().resolveThemeAttribute(
resid, outValue, resolveRefs);
+ restoreResources(changed);
+ return found;
+ }
+
+ // ---- private helper methods ----
+
+ private static boolean setupResources(Theme thisTheme) {
+ Resources_Theme_Delegate themeDelegate = sManager.getDelegate(thisTheme.getNativeTheme());
+ StyleResourceValue style = resolveStyle(themeDelegate.mThemeResId);
+ if (style != null) {
+ RenderSessionImpl.getCurrentContext().getRenderResources()
+ .applyStyle(style, themeDelegate.force);
+ return true;
+ }
+ return false;
+ }
+
+ private static void restoreResources(boolean changed) {
+ if (changed) {
+ RenderSessionImpl.getCurrentContext().getRenderResources().clearStyles();
+ }
+ }
+
+ @Nullable
+ private static StyleResourceValue resolveStyle(int nativeResid) {
+ if (nativeResid == 0) {
+ return null;
+ }
+ BridgeContext context = RenderSessionImpl.getCurrentContext();
+ ResourceReference theme = context.resolveId(nativeResid);
+ if (theme.isFramework()) {
+ return (StyleResourceValue) context.getRenderResources()
+ .getFrameworkResource(ResourceType.STYLE, theme.getName());
+ } else {
+ return (StyleResourceValue) context.getRenderResources()
+ .getProjectResource(ResourceType.STYLE, theme.getName());
+ }
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
new file mode 100644
index 0000000..802cf1c
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
@@ -0,0 +1,237 @@
+/*
+ * 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.graphics;
+
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.Rectangle2D;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.ibm.icu.lang.UScript;
+import com.ibm.icu.lang.UScriptRun;
+
+import android.graphics.Paint_Delegate.FontInfo;
+
+/**
+ * Render the text by breaking it into various scripts and using the right font for each script.
+ * Can be used to measure the text without actually drawing it.
+ */
+@SuppressWarnings("deprecation")
+public class BidiRenderer {
+
+ /* package */ static class ScriptRun {
+ int start;
+ int limit;
+ boolean isRtl;
+ int scriptCode;
+ FontInfo font;
+
+ public ScriptRun(int start, int limit, boolean isRtl) {
+ this.start = start;
+ this.limit = limit;
+ this.isRtl = isRtl;
+ this.scriptCode = UScript.INVALID_CODE;
+ }
+ }
+
+ private Graphics2D mGraphics;
+ private Paint_Delegate mPaint;
+ private char[] mText;
+ // Bounds of the text drawn so far.
+ private RectF mBounds;
+ private float mBaseline;
+
+ /**
+ * @param graphics May be null.
+ * @param paint The Paint to use to get the fonts. Should not be null.
+ * @param text Unidirectional text. Should not be null.
+ */
+ /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) {
+ assert (paint != null);
+ mGraphics = graphics;
+ mPaint = paint;
+ mText = text;
+ }
+
+ /**
+ * Render unidirectional text.
+ *
+ * This method can also be used to measure the width of the text without actually drawing it.
+ *
+ * @param start index of the first character
+ * @param limit index of the first character that should not be rendered.
+ * @param isRtl is the text right-to-left
+ * @param advances If not null, then advances for each character to be rendered are returned
+ * here.
+ * @param advancesIndex index into advances from where the advances need to be filled.
+ * @param draw If true and {@code graphics} is not null, draw the rendered text on the graphics
+ * at the given co-ordinates
+ * @param x The x-coordinate of the left edge of where the text should be drawn on the given
+ * graphics.
+ * @param y The y-coordinate at which to draw the text on the given mGraphics.
+ * @return A rectangle specifying the bounds of the text drawn.
+ */
+ /* package */ RectF renderText(int start, int limit, boolean isRtl, float[] advances,
+ int advancesIndex, boolean draw, float x, float y) {
+ // We break the text into scripts and then select font based on it and then render each of
+ // the script runs.
+ mBounds = new RectF(x, y, x, y);
+ mBaseline = y;
+ for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mPaint.getFonts())) {
+ int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT;
+ flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT;
+ renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw);
+ advancesIndex += run.limit - run.start;
+ }
+ return mBounds;
+ }
+
+ /**
+ * Render a script run to the right of the bounds passed. Use the preferred font to render as
+ * much as possible. This also implements a fallback mechanism to render characters that cannot
+ * be drawn using the preferred font.
+ */
+ private void renderScript(int start, int limit, FontInfo preferredFont, int flag,
+ float[] advances, int advancesIndex, boolean draw) {
+ List<FontInfo> fonts = mPaint.getFonts();
+ if (fonts == null || preferredFont == null) {
+ return;
+ }
+
+ while (start < limit) {
+ boolean foundFont = false;
+ int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(mText, start, limit);
+ if (canDisplayUpTo == -1) {
+ // We can draw all characters in the text.
+ render(start, limit, preferredFont, flag, advances, advancesIndex, draw);
+ return;
+ }
+ if (canDisplayUpTo > start) {
+ // We can draw something.
+ render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, draw);
+ advancesIndex += canDisplayUpTo - start;
+ start = canDisplayUpTo;
+ }
+
+ // The current character cannot be drawn with the preferred font. Cycle through all the
+ // fonts to check which one can draw it.
+ int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1;
+ for (FontInfo font : fonts) {
+ canDisplayUpTo = font.mFont.canDisplayUpTo(mText, start, start + charCount);
+ if (canDisplayUpTo == -1) {
+ render(start, start+charCount, font, flag, advances, advancesIndex, draw);
+ start += charCount;
+ advancesIndex += charCount;
+ foundFont = true;
+ break;
+ }
+ }
+ if (!foundFont) {
+ // No font can display this char. Use the preferred font. The char will most
+ // probably appear as a box or a blank space. We could, probably, use some
+ // heuristics and break the character into the base character and diacritics and
+ // then draw it, but it's probably not worth the effort.
+ render(start, start + charCount, preferredFont, flag, advances, advancesIndex,
+ draw);
+ start += charCount;
+ advancesIndex += charCount;
+ }
+ }
+ }
+
+ /**
+ * Renders the text to the right of the bounds with the given font.
+ * @param font The font to render the text with.
+ */
+ private void render(int start, int limit, FontInfo font, int flag, float[] advances,
+ int advancesIndex, boolean draw) {
+
+ // Since the metrics don't have anti-aliasing set, we create a new FontRenderContext with
+ // the anti-aliasing set.
+ FontRenderContext f = font.mMetrics.getFontRenderContext();
+ FontRenderContext frc = new FontRenderContext(f.getTransform(), mPaint.isAntiAliased(),
+ f.usesFractionalMetrics());
+ GlyphVector gv = font.mFont.layoutGlyphVector(frc, mText, start, limit, flag);
+ int ng = gv.getNumGlyphs();
+ int[] ci = gv.getGlyphCharIndices(0, ng, null);
+ if (advances != null) {
+ for (int i = 0; i < ng; i++) {
+ int adv_idx = advancesIndex + ci[i];
+ advances[adv_idx] += gv.getGlyphMetrics(i).getAdvanceX();
+ }
+ }
+ if (draw && mGraphics != null) {
+ mGraphics.drawGlyphVector(gv, mBounds.right, mBaseline);
+ }
+
+ // Update the bounds.
+ Rectangle2D awtBounds = gv.getLogicalBounds();
+ RectF bounds = awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline);
+ // If the width of the bounds is zero, no text had been drawn earlier. Hence, use the
+ // coordinates from the bounds as an offset.
+ if (Math.abs(mBounds.right - mBounds.left) == 0) {
+ mBounds = bounds;
+ } else {
+ mBounds.union(bounds);
+ }
+ }
+
+ // --- Static helper methods ---
+
+ private static RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY) {
+ float left = (float) awtRec.getX();
+ float top = (float) awtRec.getY();
+ float right = (float) (left + awtRec.getWidth());
+ float bottom = (float) (top + awtRec.getHeight());
+ RectF androidRect = new RectF(left, top, right, bottom);
+ androidRect.offset(offsetX, offsetY);
+ return androidRect;
+ }
+
+ /* package */ static List<ScriptRun> getScriptRuns(char[] text, int start, int limit,
+ boolean isRtl, List<FontInfo> fonts) {
+ LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>();
+
+ int count = limit - start;
+ UScriptRun uScriptRun = new UScriptRun(text, start, count);
+ while (uScriptRun.next()) {
+ int scriptStart = uScriptRun.getScriptStart();
+ int scriptLimit = uScriptRun.getScriptLimit();
+ ScriptRun run = new ScriptRun(scriptStart, scriptLimit, isRtl);
+ run.scriptCode = uScriptRun.getScriptCode();
+ setScriptFont(text, run, fonts);
+ scriptRuns.add(run);
+ }
+
+ return scriptRuns;
+ }
+
+ // TODO: Replace this method with one which returns the font based on the scriptCode.
+ private static void setScriptFont(char[] text, ScriptRun run,
+ List<FontInfo> fonts) {
+ for (FontInfo fontInfo : fonts) {
+ if (fontInfo.mFont.canDisplayUpTo(text, run.start, run.limit) == -1) {
+ run.font = fontInfo;
+ return;
+ }
+ }
+ run.font = fonts.get(0);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index b76b8cf..9c7a0cc 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -61,6 +61,7 @@
private final Config mConfig;
private BufferedImage mImage;
private boolean mHasAlpha = true;
+ private boolean mHasMipMap = false; // TODO: check the default.
private int mGenerationId = 0;
@@ -185,6 +186,10 @@
return mHasAlpha && mConfig != Config.RGB_565;
}
+ public boolean hasMipMap() {
+ // TODO: check if more checks are required as in hasAlpha.
+ return mHasMipMap;
+ }
/**
* Update the generationId.
*
@@ -248,8 +253,9 @@
}
@LayoutlibDelegate
- /*package*/ static void nativeRecycle(int nativeBitmap) {
+ /*package*/ static boolean nativeRecycle(int nativeBitmap) {
sManager.removeJavaReferenceFor(nativeBitmap);
+ return true;
}
@LayoutlibDelegate
@@ -336,6 +342,17 @@
}
@LayoutlibDelegate
+ /*package*/ static boolean nativeHasMipMap(int nativeBitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return true;
+ }
+
+ return delegate.mHasMipMap;
+ }
+
+ @LayoutlibDelegate
/*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) {
// get the delegate from the native int.
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
@@ -469,6 +486,17 @@
}
@LayoutlibDelegate
+ /*package*/ static void nativeSetHasMipMap(int nativeBitmap, boolean hasMipMap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mHasMipMap = hasMipMap;
+ }
+
+ @LayoutlibDelegate
/*package*/ static boolean nativeSameAs(int nb0, int nb1) {
Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
if (delegate1 == null) {
@@ -524,7 +552,7 @@
int nativeInt = sManager.addNewDelegate(delegate);
// and create/return a new Bitmap with it
- return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/,
+ return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/,
density);
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 9aed8c8..9a51817 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -23,7 +23,6 @@
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.graphics.Bitmap.Config;
-import android.graphics.Paint_Delegate.FontInfo;
import android.text.TextUtils;
import java.awt.Color;
@@ -35,7 +34,6 @@
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.image.BufferedImage;
-import java.util.List;
/**
@@ -330,20 +328,12 @@
}
@LayoutlibDelegate
- /*package*/ static void native_setBitmap(int nativeCanvas, int bitmap) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
+ /*package*/ static void native_setBitmap(int nativeCanvas, int nativeBitmap) {
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+ if (canvasDelegate != null && bitmapDelegate != null) {
+ canvasDelegate.setBitmap(bitmapDelegate);
}
-
- // get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
- if (bitmapDelegate == null) {
- return;
- }
-
- canvasDelegate.setBitmap(bitmapDelegate);
}
@LayoutlibDelegate
@@ -571,17 +561,15 @@
}
@LayoutlibDelegate
- /*package*/ static boolean native_quickReject(int nativeCanvas,
- RectF rect,
- int native_edgeType) {
+ /*package*/ static boolean native_quickReject(int nativeCanvas, RectF rect,
+ int native_edgeType) {
// FIXME properly implement quickReject
return false;
}
@LayoutlibDelegate
- /*package*/ static boolean native_quickReject(int nativeCanvas,
- int path,
- int native_edgeType) {
+ /*package*/ static boolean native_quickReject(int nativeCanvas, int path,
+ int native_edgeType) {
// FIXME properly implement quickReject
return false;
}
@@ -982,7 +970,8 @@
@LayoutlibDelegate
/*package*/ static void native_drawText(int nativeCanvas,
final char[] text, final int index, final int count,
- final float startX, final float startY, int flags, int paint) {
+ final float startX, final float startY, final int flags, int paint) {
+
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
new GcSnapshot.Drawable() {
@Override
@@ -992,9 +981,11 @@
// Paint.TextAlign indicates how the text is positioned relative to X.
// LEFT is the default and there's nothing to do.
float x = startX;
- float y = startY;
+ int limit = index + count;
+ boolean isRtl = flags == Canvas.DIRECTION_RTL;
if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
- float m = paintDelegate.measureText(text, index, count);
+ RectF bounds = paintDelegate.measureText(text, index, count, isRtl);
+ float m = bounds.right - bounds.left;
if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
x -= m / 2;
} else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
@@ -1002,87 +993,15 @@
}
}
- List<FontInfo> fonts = paintDelegate.getFonts();
-
- if (fonts.size() > 0) {
- FontInfo mainFont = fonts.get(0);
- int i = index;
- int lastIndex = index + count;
- while (i < lastIndex) {
- // always start with the main font.
- int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
- if (upTo == -1) {
- // draw all the rest and exit.
- graphics.setFont(mainFont.mFont);
- graphics.drawChars(text, i, lastIndex - i, (int)x, (int)y);
- return;
- } else if (upTo > 0) {
- // draw what's possible
- graphics.setFont(mainFont.mFont);
- graphics.drawChars(text, i, upTo - i, (int)x, (int)y);
-
- // compute the width that was drawn to increase x
- x += mainFont.mMetrics.charsWidth(text, i, upTo - i);
-
- // move index to the first non displayed char.
- i = upTo;
-
- // don't call continue at this point. Since it is certain the main font
- // cannot display the font a index upTo (now ==i), we move on to the
- // fallback fonts directly.
- }
-
- // no char supported, attempt to read the next char(s) with the
- // fallback font. In this case we only test the first character
- // and then go back to test with the main font.
- // Special test for 2-char characters.
- boolean foundFont = false;
- for (int f = 1 ; f < fonts.size() ; f++) {
- FontInfo fontInfo = fonts.get(f);
-
- // need to check that the font can display the character. We test
- // differently if the char is a high surrogate.
- int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
- upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
- if (upTo == -1) {
- // draw that char
- graphics.setFont(fontInfo.mFont);
- graphics.drawChars(text, i, charCount, (int)x, (int)y);
-
- // update x
- x += fontInfo.mMetrics.charsWidth(text, i, charCount);
-
- // update the index in the text, and move on
- i += charCount;
- foundFont = true;
- break;
-
- }
- }
-
- // in case no font can display the char, display it with the main font.
- // (it'll put a square probably)
- if (foundFont == false) {
- int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
-
- graphics.setFont(mainFont.mFont);
- graphics.drawChars(text, i, charCount, (int)x, (int)y);
-
- // measure it to advance x
- x += mainFont.mMetrics.charsWidth(text, i, charCount);
-
- // and move to the next chars.
- i += charCount;
- }
- }
- }
+ new BidiRenderer(graphics, paintDelegate, text).renderText(
+ index, limit, isRtl, null, 0, true, x, startY);
}
});
}
@LayoutlibDelegate
/*package*/ static void native_drawText(int nativeCanvas, String text,
- int start, int end, float x, float y, int flags, int paint) {
+ int start, int end, float x, float y, final int flags, int paint) {
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
index be27b54..390044a 100644
--- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -159,7 +159,7 @@
/*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance,
byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) {
draw(canvas_instance,
- (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(),
+ (int) loc.left, (int) loc.top, (int) loc.right, (int) loc.bottom,
bitmap_instance, c, paint_instance_or_null,
destDensity, srcDensity);
}
@@ -168,7 +168,7 @@
/*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance,
byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) {
draw(canvas_instance,
- loc.left, loc.top, loc.width(), loc.height(),
+ loc.left, loc.top, loc.right, loc.bottom,
bitmap_instance, c, paint_instance_or_null,
destDensity, srcDensity);
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index 1382641..245617b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -32,7 +32,6 @@
import java.awt.Toolkit;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
-import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -576,7 +575,8 @@
return 0;
}
- return delegate.measureText(text, index, count);
+ RectF bounds = delegate.measureText(text, index, count, false /*isRtl*/);
+ return bounds.right - bounds.left;
}
@LayoutlibDelegate
@@ -614,7 +614,8 @@
}
// measure from start to end
- float res = delegate.measureText(text, start, end - start + 1);
+ RectF bounds = delegate.measureText(text, start, end - start + 1, false /*isRtl*/);
+ float res = bounds.right - bounds.left;
if (measuredWidth != null) {
measuredWidth[measureIndex] = res;
@@ -964,7 +965,8 @@
@LayoutlibDelegate
/*package*/ static int native_getTextWidths(int native_object, String text, int start,
int end, float[] widths) {
- return native_getTextWidths(native_object, text.toCharArray(), start, end - start, widths);
+ return native_getTextWidths(native_object, text.toCharArray(), start, end - start,
+ widths);
}
@LayoutlibDelegate
@@ -978,51 +980,28 @@
/*package*/ static float native_getTextRunAdvances(int native_object,
char[] text, int index, int count, int contextIndex, int contextCount,
int flags, float[] advances, int advancesIndex, int reserved) {
+
+ if (advances != null)
+ for (int i = advancesIndex; i< advancesIndex+count; i++)
+ advances[i]=0;
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
+ if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) {
return 0.f;
}
+ boolean isRtl = isRtl(flags);
- if (delegate.mFonts.size() > 0) {
- // FIXME: handle multi-char characters (see measureText)
- float totalAdvance = 0;
- for (int i = 0; i < count; i++) {
- char c = text[i + index];
- boolean found = false;
- for (FontInfo info : delegate.mFonts) {
- if (info.mFont.canDisplay(c)) {
- float adv = info.mMetrics.charWidth(c);
- totalAdvance += adv;
- if (advances != null) {
- advances[i] = adv;
- }
-
- found = true;
- break;
- }
- }
-
- if (found == false) {
- // no advance for this char.
- if (advances != null) {
- advances[i] = 0.f;
- }
- }
- }
-
- return totalAdvance;
- }
-
- return 0;
-
+ int limit = index + count;
+ RectF bounds = new BidiRenderer(null, delegate, text).renderText(
+ index, limit, isRtl, advances, advancesIndex, false, 0, 0);
+ return bounds.right - bounds.left;
}
@LayoutlibDelegate
/*package*/ static float native_getTextRunAdvances(int native_object,
String text, int start, int end, int contextStart, int contextEnd,
int flags, float[] advances, int advancesIndex, int reserved) {
- // FIXME: support contextStart, contextEnd and direction flag
+ // FIXME: support contextStart and contextEnd
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
@@ -1068,7 +1047,8 @@
@LayoutlibDelegate
/*package*/ static void nativeGetStringBounds(int nativePaint, String text, int start,
int end, Rect bounds) {
- nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start, bounds);
+ nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start,
+ bounds);
}
@LayoutlibDelegate
@@ -1077,19 +1057,11 @@
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
+ if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) {
return;
}
- // FIXME should test if the main font can display all those characters.
- // See MeasureText
- if (delegate.mFonts.size() > 0) {
- FontInfo mainInfo = delegate.mFonts.get(0);
-
- Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count,
- delegate.mFontContext);
- bounds.set(0, 0, (int) rect.getWidth(), (int) rect.getHeight());
- }
+ delegate.measureText(text, index, count, false /*isRtl*/).roundOut(bounds);
}
@LayoutlibDelegate
@@ -1173,6 +1145,7 @@
info.mFont = info.mFont.deriveFont(new AffineTransform(
mTextScaleX, mTextSkewX, 0, 1, 0, 0));
}
+ // The metrics here don't have anti-aliasing set.
info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);
infoList.add(info);
@@ -1182,63 +1155,9 @@
}
}
- /*package*/ float measureText(char[] text, int index, int count) {
-
- // WARNING: the logic in this method is similar to Canvas_Delegate.native_drawText
- // Any change to this method should be reflected there as well
-
- if (mFonts.size() > 0) {
- FontInfo mainFont = mFonts.get(0);
- int i = index;
- int lastIndex = index + count;
- float total = 0f;
- while (i < lastIndex) {
- // always start with the main font.
- int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
- if (upTo == -1) {
- // shortcut to exit
- return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i);
- } else if (upTo > 0) {
- total += mainFont.mMetrics.charsWidth(text, i, upTo - i);
- i = upTo;
- // don't call continue at this point. Since it is certain the main font
- // cannot display the font a index upTo (now ==i), we move on to the
- // fallback fonts directly.
- }
-
- // no char supported, attempt to read the next char(s) with the
- // fallback font. In this case we only test the first character
- // and then go back to test with the main font.
- // Special test for 2-char characters.
- boolean foundFont = false;
- for (int f = 1 ; f < mFonts.size() ; f++) {
- FontInfo fontInfo = mFonts.get(f);
-
- // need to check that the font can display the character. We test
- // differently if the char is a high surrogate.
- int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
- upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
- if (upTo == -1) {
- total += fontInfo.mMetrics.charsWidth(text, i, charCount);
- i += charCount;
- foundFont = true;
- break;
-
- }
- }
-
- // in case no font can display the char, measure it with the main font.
- if (foundFont == false) {
- int size = Character.isHighSurrogate(text[i]) ? 2 : 1;
- total += mainFont.mMetrics.charsWidth(text, i, size);
- i += size;
- }
- }
-
- return total;
- }
-
- return 0;
+ /*package*/ RectF measureText(char[] text, int index, int count, boolean isRtl) {
+ return new BidiRenderer(null, this, text).renderText(
+ index, index + count, isRtl, null, 0, false, 0, 0);
}
private float getFontMetrics(FontMetrics metrics) {
@@ -1277,4 +1196,14 @@
}
}
+ private static boolean isRtl(int flag) {
+ switch(flag) {
+ case Paint.BIDI_RTL:
+ case Paint.BIDI_FORCE_RTL:
+ case Paint.BIDI_DEFAULT_RTL:
+ return true;
+ default:
+ 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 2414d70..9b291cf 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -103,6 +103,9 @@
if (familyName == null) {
familyName = DEFAULT_FAMILY;
}
+ if (style < 0) {
+ style = Typeface.NORMAL;
+ }
Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style);
if (sFontLoader != null) {
diff --git a/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java b/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java
index 2961f97..09f3e47 100644
--- a/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java
+++ b/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java
@@ -15,6 +15,8 @@
*/
package android.os;
+import java.lang.reflect.Field;
+
/**
* Class allowing access to package-protected methods/fields.
*/
@@ -23,5 +25,23 @@
public static void cleanupThread() {
// clean up the looper
Looper.sThreadLocal.remove();
+ try {
+ Field sMainLooper = Looper.class.getDeclaredField("sMainLooper");
+ sMainLooper.setAccessible(true);
+ sMainLooper.set(null, null);
+ } catch (SecurityException e) {
+ catchReflectionException();
+ } catch (IllegalArgumentException e) {
+ catchReflectionException();
+ } catch (NoSuchFieldException e) {
+ catchReflectionException();
+ } catch (IllegalAccessException e) {
+ catchReflectionException();
+ }
+
+ }
+
+ private static void catchReflectionException() {
+ assert(false);
}
}
diff --git a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
index 52b8f34..973fa0e 100644
--- a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
@@ -16,7 +16,10 @@
package android.text;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.ibm.icu.text.Bidi;
/**
@@ -29,9 +32,29 @@
public class AndroidBidi_Delegate {
@LayoutlibDelegate
- /*package*/ static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo) {
- // return the equivalent of Layout.DIR_LEFT_TO_RIGHT
- // TODO: actually figure the direction.
- return 0;
+ /*package*/ static int runBidi(int dir, char[] chars, byte[] charInfo, int count,
+ boolean haveInfo) {
+
+ switch (dir) {
+ case 0: // Layout.DIR_REQUEST_LTR
+ case 1: // Layout.DIR_REQUEST_RTL
+ break; // No change.
+ case -1:
+ dir = Bidi.LEVEL_DEFAULT_LTR;
+ break;
+ case -2:
+ dir = Bidi.LEVEL_DEFAULT_RTL;
+ break;
+ default:
+ // Invalid code. Log error, assume LEVEL_DEFAULT_LTR and continue.
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Invalid direction flag", null);
+ dir = Bidi.LEVEL_DEFAULT_LTR;
+ }
+ Bidi bidi = new Bidi(chars, 0, null, 0, count, dir);
+ if (charInfo != null) {
+ for (int i = 0; i < count; ++i)
+ charInfo[i] = bidi.getLevelAt(i);
+ }
+ return bidi.getParaLevel();
}
}
diff --git a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java
new file mode 100644
index 0000000..320dd0d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java
@@ -0,0 +1,160 @@
+/*
+ * 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>";
+
+ @LayoutlibDelegate
+ /*package*/ static long normalize(Time thisTime, boolean ignoreDst) {
+ long millis = toMillis(thisTime, ignoreDst);
+ set(thisTime, millis);
+ return millis;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void switchTimezone(Time thisTime, String timezone) {
+ Calendar c = timeToCalendar(thisTime);
+ c.setTimeZone(TimeZone.getTimeZone(timezone));
+ calendarToTime(c, thisTime);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCompare(Time a, Time b) {
+ return timeToCalendar(a).compareTo(timeToCalendar(b));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String format1(Time thisTime, String format) {
+
+ try {
+ // Change the format by adding changing '%' to "%1$t". This is required to tell the
+ // formatter which argument to use from the argument list. '%%' is left as is. In the
+ // replacement string, $0 refers to matched pattern. \\1 means '1', written this way to
+ // separate it from 0. \\$ means '$', written this way to suppress the special meaning
+ // of $.
+ return String.format(
+ p.matcher(format).replaceAll("$0\\1\\$t"),
+ timeToCalendar(thisTime));
+ } catch (UnknownFormatConversionException e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_STRFTIME, "Unrecognized format", e, format);
+ return format;
+ }
+ }
+
+ /**
+ * Return the current time in YYYYMMDDTHHMMSS<tz> format
+ */
+ @LayoutlibDelegate
+ /*package*/ static String toString(Time thisTime) {
+ Calendar c = timeToCalendar(thisTime);
+ return String.format(FORMAT, c);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeParse(Time thisTime, String s) {
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "android.text.format.Time.parse() not supported.", null);
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeParse3339(Time thisTime, String s) {
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "android.text.format.Time.parse3339() not supported.", null);
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setToNow(Time thisTime) {
+ calendarToTime(getCalendarInstance(thisTime), thisTime);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long toMillis(Time thisTime, boolean ignoreDst) {
+ // TODO: Respect ignoreDst.
+ return timeToCalendar(thisTime).getTimeInMillis();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void set(Time thisTime, long millis) {
+ Calendar c = getCalendarInstance(thisTime);
+ c.setTimeInMillis(millis);
+ calendarToTime(c,thisTime);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String format2445(Time thisTime) {
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "android.text.format.Time.format2445() not supported.", null);
+ return "";
+ }
+
+ // ---- 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/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 941f1ce6..a2e93a7 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -32,10 +32,6 @@
import android.content.Context;
import android.util.AttributeSet;
-import android.view.InflateException;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
import java.io.File;
@@ -154,6 +150,9 @@
@Override
public View inflate(int resource, ViewGroup root) {
Context context = getContext();
+ if (context instanceof ContextThemeWrapper) {
+ context = ((ContextThemeWrapper) context).getBaseContext();
+ }
if (context instanceof BridgeContext) {
BridgeContext bridgeContext = (BridgeContext)context;
@@ -216,43 +215,16 @@
}
private void setupViewInContext(View view, AttributeSet attrs) {
- if (getContext() instanceof BridgeContext) {
- BridgeContext bc = (BridgeContext) getContext();
- if (attrs instanceof BridgeXmlBlockParser) {
- BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs;
-
- // get the view key
- Object viewKey = parser.getViewCookie();
-
- if (viewKey == null) {
- int currentDepth = parser.getDepth();
-
- // test whether we are in an included file or in a adapter binding view.
- BridgeXmlBlockParser previousParser = bc.getPreviousParser();
- if (previousParser != null) {
- // looks like we inside an embedded layout.
- // only apply the cookie of the calling node (<include>) if we are at the
- // top level of the embedded layout. If there is a merge tag, then
- // skip it and look for the 2nd level
- int testDepth = mIsInMerge ? 2 : 1;
- if (currentDepth == testDepth) {
- viewKey = previousParser.getViewCookie();
- // if we are in a merge, wrap the cookie in a MergeCookie.
- if (viewKey != null && mIsInMerge) {
- viewKey = new MergeCookie(viewKey);
- }
- }
- } else if (mResourceReference != null && currentDepth == 1) {
- // else if there's a resource reference, this means we are in an adapter
- // binding case. Set the resource ref as the view cookie only for the top
- // level view.
- viewKey = mResourceReference;
- }
- }
-
- if (viewKey != null) {
- bc.addViewKey(view, viewKey);
- }
+ Context context = getContext();
+ if (context instanceof ContextThemeWrapper) {
+ context = ((ContextThemeWrapper) context).getBaseContext();
+ }
+ if (context instanceof BridgeContext) {
+ BridgeContext bc = (BridgeContext) context;
+ // get the view key
+ Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
+ if (viewKey != null) {
+ bc.addViewKey(view, viewKey);
}
}
}
@@ -269,4 +241,44 @@
public LayoutInflater cloneInContext(Context newContext) {
return new BridgeInflater(this, newContext);
}
+
+ /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc,
+ ResourceReference resourceReference, boolean isInMerge) {
+
+ if (!(attrs instanceof BridgeXmlBlockParser)) {
+ return null;
+ }
+ BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs);
+
+ // get the view key
+ Object viewKey = parser.getViewCookie();
+
+ if (viewKey == null) {
+ int currentDepth = parser.getDepth();
+
+ // test whether we are in an included file or in a adapter binding view.
+ BridgeXmlBlockParser previousParser = bc.getPreviousParser();
+ if (previousParser != null) {
+ // looks like we are inside an embedded layout.
+ // only apply the cookie of the calling node (<include>) if we are at the
+ // top level of the embedded layout. If there is a merge tag, then
+ // skip it and look for the 2nd level
+ int testDepth = isInMerge ? 2 : 1;
+ if (currentDepth == testDepth) {
+ viewKey = previousParser.getViewCookie();
+ // if we are in a merge, wrap the cookie in a MergeCookie.
+ if (viewKey != null && isInMerge) {
+ viewKey = new MergeCookie(viewKey);
+ }
+ }
+ } else if (resourceReference != null && currentDepth == 1) {
+ // else if there's a resource reference, this means we are in an adapter
+ // binding case. Set the resource ref as the view cookie only for the top
+ // level view.
+ viewKey = resourceReference;
+ }
+ }
+
+ return viewKey;
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
new file mode 100644
index 0000000..e34ad38b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
@@ -0,0 +1,75 @@
+/*
+ * 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.content.Context;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.internal.view.menu.BridgeMenuItemImpl;
+import com.android.internal.view.menu.MenuView;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.AttributeSet;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link MenuInflater}
+ * <p/>
+ * Through the layoutlib_create tool, the original methods of MenuInflater have been
+ * replaced by calls to methods of the same name in this delegate class.
+ * <p/>
+ * The main purpose of the class is to get the view key from the menu xml parser and add it to
+ * the menu item. The view key is used by the IDE to match the individual view elements to the
+ * corresponding xml tag in the menu/layout file.
+ * <p/>
+ * For Menus, the views may be reused and the {@link MenuItem} is a better object to hold the
+ * view key than the {@link MenuView.ItemView}. At the time of computation of the rest of {@link
+ * ViewInfo}, we check the corresponding view key in the menu item for the view and add it
+ */
+public class MenuInflater_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem,
+ AttributeSet attrs) {
+ if (menuItem instanceof BridgeMenuItemImpl) {
+ Context context = thisInflater.getContext();
+ if (context instanceof ContextThemeWrapper) {
+ context = ((ContextThemeWrapper) context).getBaseContext();
+ }
+ if (context instanceof BridgeContext) {
+ Object viewKey = BridgeInflater.getViewKeyFromParser(
+ attrs, ((BridgeContext) context), null, false);
+ ((BridgeMenuItemImpl) menuItem).setViewCookie(viewKey);
+ return;
+ }
+ }
+ // This means that Bridge did not take over the instantiation of some object properly.
+ // This is most likely a bug in the LayoutLib code.
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+ "Action Bar Menu rendering may be incorrect.", null);
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void registerMenu(MenuInflater thisInflater, SubMenu subMenu,
+ AttributeSet parser) {
+ registerMenu(thisInflater, subMenu.getItem(), parser);
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
new file mode 100644
index 0000000..cdb839a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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.internal.view.menu;
+
+import com.android.layoutlib.bridge.android.BridgeContext;
+
+import android.content.Context;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+
+/**
+ * An extension of the {@link MenuItemImpl} to store the view cookie also.
+ */
+public class BridgeMenuItemImpl extends MenuItemImpl {
+
+ /**
+ * An object returned by the IDE that helps mapping each View to the corresponding XML tag in
+ * the layout. For Menus, we store this cookie here and attach it to the corresponding view
+ * at the time of rendering.
+ */
+ private Object viewCookie;
+ private BridgeContext mContext;
+
+ /**
+ * Instantiates this menu item.
+ */
+ BridgeMenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
+ CharSequence title, int showAsAction) {
+ super(menu, group, id, categoryOrder, ordering, title, showAsAction);
+ Context context = menu.getContext();
+ if (context instanceof ContextThemeWrapper) {
+ context = ((ContextThemeWrapper) context).getBaseContext();
+ }
+ if (context instanceof BridgeContext) {
+ mContext = ((BridgeContext) context);
+ }
+ }
+
+ public Object getViewCookie() {
+ return viewCookie;
+ }
+
+ public void setViewCookie(Object viewCookie) {
+ // If the menu item has an associated action provider view,
+ // directly set the cookie in the view to cookie map stored in BridgeContext.
+ View actionView = getActionView();
+ if (actionView != null && mContext != null) {
+ mContext.addViewKey(actionView, viewCookie);
+ // We don't need to add the view cookie to the this item now. But there's no harm in
+ // storing it, in case we need it in the future.
+ }
+ this.viewCookie = viewCookie;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilderAccessor.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilderAccessor.java
new file mode 100644
index 0000000..f0798cb
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilderAccessor.java
@@ -0,0 +1,12 @@
+package com.android.internal.view.menu;
+
+import java.util.ArrayList;
+
+/**
+ * To access non public members of {@link MenuBuilder}
+ */
+public class MenuBuilderAccessor {
+ public static ArrayList<MenuItemImpl> getNonActionItems(MenuBuilder builder) {
+ return builder.getNonActionItems();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java
new file mode 100644
index 0000000..505fb81
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java
@@ -0,0 +1,38 @@
+/*
+ * 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.internal.view.menu;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link MenuBuilder}
+ * <p/>
+ * Through the layoutlib_create tool, the original methods of {@code MenuBuilder} have been
+ * replaced by calls to methods of the same name in this delegate class.
+ */
+public class MenuBuilder_Delegate {
+ /**
+ * The method overrides the instantiation of the {@link MenuItemImpl} with an instance of
+ * {@link BridgeMenuItemImpl} so that view cookies may be stored.
+ */
+ @LayoutlibDelegate
+ /*package*/ static MenuItemImpl createNewMenuItem(MenuBuilder thisMenu, int group, int id,
+ int categoryOrder, int ordering, CharSequence title, int defaultShowAsAction) {
+ return new BridgeMenuItemImpl(thisMenu, group, id, categoryOrder, ordering, title,
+ defaultShowAsAction);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java b/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java
new file mode 100644
index 0000000..47a3679
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.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 com.android.internal.widget;
+
+import com.android.internal.view.menu.ActionMenuPresenter;
+
+/**
+ * To access non public members of AbsActionBarView
+ */
+public class ActionBarAccessor {
+
+ /**
+ * Returns the {@link ActionMenuPresenter} associated with the {@link AbsActionBarView}
+ */
+ public static ActionMenuPresenter getActionMenuPresenter(AbsActionBarView view) {
+ return view.mActionMenuPresenter;
+ }
+}
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 bf8658e..fa8050f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -35,6 +35,7 @@
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 android.content.res.BridgeAssetManager;
import android.graphics.Bitmap;
@@ -64,6 +65,8 @@
*/
public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
+ private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
+
public static class StaticMethodNotImplementedException extends RuntimeException {
private static final long serialVersionUID = 1L;
@@ -211,7 +214,9 @@
Capability.ANIMATED_VIEW_MANIPULATION,
Capability.ADAPTER_BINDING,
Capability.EXTENDED_VIEWINFO,
- Capability.FIXED_SCALABLE_NINE_PATCH);
+ Capability.FIXED_SCALABLE_NINE_PATCH,
+ Capability.RTL,
+ Capability.ACTION_BAR);
BridgeAssetManager.initSystem();
@@ -411,6 +416,20 @@
throw new IllegalArgumentException("viewObject is not a View");
}
+ @Override
+ public boolean isRtl(String locale) {
+ return isLocaleRtl(locale);
+ }
+
+ public static boolean isLocaleRtl(String locale) {
+ if (locale == null) {
+ locale = "";
+ }
+ ULocale uLocale = new ULocale(locale);
+ return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL) ?
+ true : false;
+ }
+
/**
* Returns the lock for the bridge
*/
@@ -428,7 +447,7 @@
// we need to make sure the Looper has been initialized for this thread.
// this is required for View that creates Handler objects.
if (Looper.myLooper() == null) {
- Looper.prepare();
+ Looper.prepareMainLooper();
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index f9f4b3a..e0f87fd 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -64,6 +64,11 @@
}
@Override
+ public List<ViewInfo> getSystemRootViews() {
+ return mSession.getSystemViewInfos();
+ }
+
+ @Override
public Map<String, String> getDefaultProperties(Object viewObject) {
return mSession.getDefaultProperties(viewObject);
}
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 e2fced6..3fcd987 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 com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.IProjectCallback;
import com.android.ide.common.rendering.api.LayoutLog;
@@ -109,7 +110,7 @@
// maps for dynamically generated id representing style objects (StyleResourceValue)
private Map<Integer, StyleResourceValue> mDynamicIdToStyleMap;
private Map<StyleResourceValue, Integer> mStyleToDynamicIdMap;
- private int mDynamicIdGenerator = 0x01030000; // Base id for framework R.style
+ 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;
@@ -132,7 +133,8 @@
RenderResources renderResources,
IProjectCallback projectCallback,
Configuration config,
- int targetSdkVersion) {
+ int targetSdkVersion,
+ boolean hasRtlSupport) {
mProjectKey = projectKey;
mMetrics = metrics;
mProjectCallback = projectCallback;
@@ -142,6 +144,9 @@
mApplicationInfo = new ApplicationInfo();
mApplicationInfo.targetSdkVersion = targetSdkVersion;
+ if (hasRtlSupport) {
+ mApplicationInfo.flags = mApplicationInfo.flags | ApplicationInfo.FLAG_SUPPORTS_RTL;
+ }
mWindowManager = new WindowManagerImpl(mMetrics);
}
@@ -311,6 +316,11 @@
}
}
+ // The base value for R.style is 0x01030000 and the custom style is 0x02030000.
+ // So, if the second byte is 03, it's probably a style.
+ if ((id >> 16 & 0xFF) == 0x03) {
+ return getStyleByDynamicId(id);
+ }
return null;
}
@@ -451,7 +461,10 @@
@Override
public final TypedArray obtainStyledAttributes(int[] attrs) {
- return createStyleBasedTypedArray(mRenderResources.getCurrentTheme(), 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.
+ return createStyleBasedTypedArray(null, attrs);
}
@Override
@@ -600,7 +613,8 @@
}
if (value != null) {
- if (value.getFirst() == ResourceType.STYLE) {
+ if ((value.getFirst() == ResourceType.STYLE)
+ || (value.getFirst() == ResourceType.ATTR)) {
// look for the style in the current theme, and its parent:
ResourceValue item = mRenderResources.findItemInTheme(value.getSecond(),
isFrameworkRes);
@@ -719,11 +733,13 @@
/**
* Creates a {@link BridgeTypedArray} by filling the values defined by the int[] with the
- * values found in the given style.
+ * values found in the given style. If no style is specified, the default theme, along with the
+ * styles applied to it are used.
+ *
* @see #obtainStyledAttributes(int, int[])
*/
- private BridgeTypedArray createStyleBasedTypedArray(StyleResourceValue style, int[] attrs)
- throws Resources.NotFoundException {
+ private BridgeTypedArray createStyleBasedTypedArray(@Nullable StyleResourceValue style,
+ int[] attrs) throws Resources.NotFoundException {
List<Pair<String, Boolean>> attributes = searchAttrs(attrs);
@@ -736,8 +752,14 @@
if (attribute != null) {
// look for the value in the given style
- ResourceValue resValue = mRenderResources.findItemInStyle(style,
- attribute.getFirst(), attribute.getSecond());
+ ResourceValue resValue;
+ if (style != null) {
+ resValue = mRenderResources.findItemInStyle(style, attribute.getFirst(),
+ attribute.getSecond());
+ } else {
+ resValue = mRenderResources.findItemInTheme(attribute.getFirst(),
+ attribute.getSecond());
+ }
if (resValue != null) {
// resolve it to make sure there are no references left.
@@ -752,7 +774,6 @@
return ta;
}
-
/**
* The input int[] attrs is a list of attributes. The returns a list of information about
* each attributes. The information is (name, isFramework)
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
new file mode 100644
index 0000000..9082e27
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
@@ -0,0 +1,385 @@
+/*
+ * 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.ide.common.rendering.api.SystemViewCookie;
+import com.android.internal.R;
+import com.android.internal.app.ActionBarImpl;
+import com.android.internal.util.Predicate;
+import com.android.internal.view.menu.ActionMenuView;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuBuilderAccessor;
+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.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+
+import java.util.ArrayList;
+
+import static com.android.ide.common.rendering.api.SystemViewCookie.ACTION_BAR_OVERFLOW;
+
+/**
+ * A layout representing the action bar.
+ */
+public class ActionBarLayout extends LinearLayout {
+
+ // 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;
+
+ // A fake parent for measuring views.
+ @Nullable private ViewGroup mMeasureParent;
+
+ public ActionBarLayout(@NonNull BridgeContext context, @NonNull SessionParams params) {
+
+ super(context);
+ setOrientation(LinearLayout.HORIZONTAL);
+ setGravity(Gravity.CENTER_VERTICAL);
+
+ // Inflate action bar layout.
+ LayoutInflater.from(context).inflate(R.layout.screen_action_bar, this,
+ true /*attachToRoot*/);
+ mActionBar = new ActionBarImpl(this);
+
+ // 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();
+ }
+
+ /**
+ * Sets up the action bar by filling the appropriate data.
+ */
+ 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);
+ }
+
+ // Add show home as up icon.
+ if (mShowHomeAsUp) {
+ mActionBar.setDisplayOptions(0xFF, ActionBar.DISPLAY_HOME_AS_UP);
+ }
+
+ // Set the navigation mode.
+ mActionBar.setNavigationMode(mNavMode);
+ if (mNavMode == ActionBar.NAVIGATION_MODE_TABS) {
+ setupTabs(3);
+ }
+
+ // Ideally we should get the icon from the mThemedContext using R.styleable.ActionBar_icon.
+ // But for simplicity, we are using the icon passed from the session params. This has been
+ // fixed in API 19.
+ if (mIcon != null) {
+ Drawable iconDrawable = getDrawable(mIcon, false /*isFramework*/);
+ if (iconDrawable != null) {
+ mActionBar.setIcon(iconDrawable);
+ }
+ }
+
+ if (mActionBarView != null) {
+ // Set action bar to be split, if needed.
+ ActionBarContainer splitView = (ActionBarContainer) findViewById(R.id.split_action_bar);
+ mActionBarView.setSplitView(splitView);
+ mActionBarView.setSplitActionBar(mSplit);
+
+ inflateMenus();
+
+ // Find if the Overflow Menu Button (the three dots) exists. If yes,
+ // add the view cookie.
+ Predicate<View> overflowMenuButtonTest = new Predicate<View>() {
+ @Override
+ public boolean apply(View view) {
+ ViewGroup.LayoutParams lp = view.getLayoutParams();
+ return lp instanceof ActionMenuView.LayoutParams &&
+ ((ActionMenuView.LayoutParams) lp).isOverflowButton;
+ }
+ };
+ View overflowMenu = null;
+ if (mSplit) {
+ if (splitView != null) {
+ overflowMenu = splitView.findViewByPredicate(overflowMenuButtonTest);
+ }
+ }
+ else {
+ overflowMenu = mActionBarView.findViewByPredicate(overflowMenuButtonTest);
+ }
+ if (overflowMenu != null) {
+ mBridgeContext.addViewKey(overflowMenu, new SystemViewCookie(ACTION_BAR_OVERFLOW));
+ }
+ }
+ }
+
+ /**
+ * 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*/);
+ }
+
+ // 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);
+ }
+ }
+
+ @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;
+ }
+
+ /**
+ * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to
+ * the content frame which shall serve as the new content root.
+ */
+ public void createMenuPopup() {
+ assert mContentRoot != null && findViewById(android.R.id.content) == mContentRoot
+ : "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);
+
+ LinearLayout layout = new LinearLayout(mThemedContext);
+ RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
+ measureContentWidth(adapter), LayoutParams.WRAP_CONTENT);
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
+ if (mSplit) {
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+ // TODO: Find correct value instead of hardcoded 10dp.
+ layoutParams.bottomMargin = getPixelValue("-10dp", metrics);
+ } else {
+ layoutParams.topMargin = getPixelValue("-10dp", metrics);
+ }
+ layout.setLayoutParams(layoutParams);
+ final TypedArray a = mThemedContext.obtainStyledAttributes(null,
+ R.styleable.PopupWindow, R.attr.popupMenuStyle, 0);
+ layout.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
+ layout.setDividerDrawable(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;
+ }
+
+ private boolean isOverflowPopupNeeded() {
+ boolean needed = mCallback.isOverflowPopupNeeded();
+ if (!needed) {
+ return false;
+ }
+ // Copied from android.widget.ActionMenuPresenter.updateMenuView()
+ ArrayList<MenuItemImpl> menus = MenuBuilderAccessor.getNonActionItems(mMenuBuilder);
+ if (ActionBarAccessor.getActionMenuPresenter(mActionBarView).isOverflowReserved() &&
+ menus != null) {
+ final int count = menus.size();
+ if (count == 1) {
+ needed = !menus.get(0).isActionViewExpanded();
+ } else {
+ needed = count > 0;
+ }
+ }
+ return needed;
+ }
+
+ @Nullable
+ public FrameLayout getContentRoot() {
+ return mContentRoot;
+ }
+
+ // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth()
+ private int measureContentWidth(@NonNull ListAdapter adapter) {
+ // Menus don't tend to be long, so this is more sane than it looks.
+ int maxWidth = 0;
+ View itemView = null;
+ int itemType = 0;
+
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int count = adapter.getCount();
+ for (int i = 0; i < count; i++) {
+ final int positionType = adapter.getItemViewType(i);
+ if (positionType != itemType) {
+ itemType = positionType;
+ itemView = null;
+ }
+
+ if (mMeasureParent == null) {
+ mMeasureParent = new FrameLayout(mThemedContext);
+ }
+
+ itemView = adapter.getView(i, itemView, mMeasureParent);
+ itemView.measure(widthMeasureSpec, heightMeasureSpec);
+
+ final int itemWidth = itemView.getMeasuredWidth();
+ if (itemWidth >= mPopupMaxWidth) {
+ return mPopupMaxWidth;
+ } else if (itemWidth > maxWidth) {
+ maxWidth = itemWidth;
+ }
+ }
+
+ return maxWidth;
+ }
+
+ private int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
+ TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/);
+ return (int) typedValue.getDimension(metrics);
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
index ea9d8d9..bcd08eb4 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -25,6 +25,7 @@
import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.Density;
+import com.android.resources.LayoutDirection;
import com.android.resources.ResourceType;
import org.xmlpull.v1.XmlPullParser;
@@ -86,38 +87,53 @@
}
}
- private InputStream getIcon(String iconName, Density[] densityInOut, String[] pathOut,
- boolean tryOtherDensities) {
+ private InputStream getIcon(String iconName, Density[] densityInOut, LayoutDirection direction,
+ String[] pathOut, boolean tryOtherDensities) {
// current density
Density density = densityInOut[0];
// bitmap url relative to this class
- pathOut[0] = "/bars/" + density.getResourceValue() + "/" + iconName;
+ if (direction != null) {
+ pathOut[0] = "/bars/" + direction.getResourceValue() + "-" + density.getResourceValue()
+ + "/" + iconName;
+ } else {
+ pathOut[0] = "/bars/" + density.getResourceValue() + "/" + iconName;
+ }
InputStream stream = getClass().getResourceAsStream(pathOut[0]);
if (stream == null && tryOtherDensities) {
for (Density d : Density.values()) {
if (d != density) {
densityInOut[0] = d;
- stream = getIcon(iconName, densityInOut, pathOut, false /*tryOtherDensities*/);
+ stream = getIcon(iconName, densityInOut, direction, pathOut,
+ false /*tryOtherDensities*/);
if (stream != null) {
return stream;
}
}
}
+ // couldn't find resource with direction qualifier. try without.
+ if (direction != null) {
+ return getIcon(iconName, densityInOut, null, pathOut, true);
+ }
}
return stream;
}
protected void loadIcon(int index, String iconName, Density density) {
+ loadIcon(index, iconName, density, false);
+ }
+
+ protected void loadIcon(int index, String iconName, Density density, boolean isRtl) {
View child = getChildAt(index);
if (child instanceof ImageView) {
ImageView imageView = (ImageView) child;
String[] pathOut = new String[1];
Density[] densityInOut = new Density[] { density };
- InputStream stream = getIcon(iconName, densityInOut, pathOut,
+ LayoutDirection dir = isRtl ? LayoutDirection.RTL : LayoutDirection.LTR;
+ InputStream stream = getIcon(iconName, densityInOut, dir, pathOut,
true /*tryOtherDensities*/);
density = densityInOut[0];
@@ -274,7 +290,7 @@
TypedValue out = new TypedValue();
if (ResourceHelper.parseFloatAttribute("textSize", textSize.getValue(), out,
true /*requireUnit*/)) {
- textView.setTextSize(
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
out.getDimension(bridgeContext.getResources().getDisplayMetrics()));
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java
deleted file mode 100644
index 226649d..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.bars;
-
-import com.android.resources.Density;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.content.Context;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class FakeActionBar extends CustomBar {
-
- private TextView mTextView;
-
- public FakeActionBar(Context context, Density density, String label, String icon)
- throws XmlPullParserException {
- super(context, density, LinearLayout.HORIZONTAL, "/bars/action_bar.xml", "action_bar.xml");
-
- // Cannot access the inside items through id because no R.id values have been
- // created for them.
- // We do know the order though.
- loadIconById(android.R.id.home, icon);
- mTextView = setText(1, label);
-
- setStyle("actionBarStyle");
- }
-
- @Override
- protected TextView getStyleableTextView() {
- return mTextView;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
index cc90d6b..84e676e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
@@ -17,6 +17,7 @@
package com.android.layoutlib.bridge.bars;
import com.android.resources.Density;
+import com.android.layoutlib.bridge.Bridge;
import org.xmlpull.v1.XmlPullParserException;
@@ -26,7 +27,8 @@
public class NavigationBar extends CustomBar {
- public NavigationBar(Context context, Density density, int orientation) throws XmlPullParserException {
+ public NavigationBar(Context context, Density density, int orientation, boolean isRtl,
+ boolean rtlEnabled) throws XmlPullParserException {
super(context, density, orientation, "/bars/navigation_bar.xml", "navigation_bar.xml");
setBackgroundColor(0xFF000000);
@@ -37,14 +39,15 @@
// 0 is a spacer.
int back = 1;
int recent = 3;
- if (orientation == LinearLayout.VERTICAL) {
+ if (orientation == LinearLayout.VERTICAL || (isRtl && !rtlEnabled)) {
+ // If RTL is enabled, then layoutlib mirrors the layout for us.
back = 3;
recent = 1;
}
- loadIcon(back, "ic_sysbar_back.png", density);
- loadIcon(2, "ic_sysbar_home.png", density);
- loadIcon(recent, "ic_sysbar_recent.png", density);
+ loadIcon(back, "ic_sysbar_back.png", density, isRtl);
+ loadIcon(2, "ic_sysbar_home.png", density, isRtl);
+ loadIcon(recent, "ic_sysbar_recent.png", density, isRtl);
}
@Override
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java
new file mode 100644
index 0000000..79e231c
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java
@@ -0,0 +1,100 @@
+/*
+ * 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.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuBuilderAccessor;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuView;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * Provides an adapter for Overflow menu popup. This is very similar to
+ * {@code MenuPopupHelper.MenuAdapter}
+ */
+public class OverflowMenuAdapter extends BaseAdapter {
+
+ private final MenuBuilder mMenu;
+ private int mExpandedIndex = -1;
+ private final Context context;
+
+ public OverflowMenuAdapter(MenuBuilder menu, Context context) {
+ mMenu = menu;
+ findExpandedIndex();
+ this.context = context;
+ }
+
+ @Override
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = MenuBuilderAccessor.getNonActionItems(mMenu);
+ if (mExpandedIndex < 0) {
+ return items.size();
+ }
+ return items.size() - 1;
+ }
+
+ @Override
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = MenuBuilderAccessor.getNonActionItems(mMenu);
+ if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
+ position++;
+ }
+ return items.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ LayoutInflater mInflater = LayoutInflater.from(context);
+ convertView = mInflater.inflate(com.android.internal.R.layout.popup_menu_item_layout,
+ parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+
+ private void findExpandedIndex() {
+ final MenuItemImpl expandedItem = mMenu.getExpandedItem();
+ if (expandedItem != null) {
+ final ArrayList<MenuItemImpl> items = MenuBuilderAccessor.getNonActionItems(mMenu);
+ final int count = items.size();
+ for (int i = 0; i < count; i++) {
+ final MenuItemImpl item = items.get(i);
+ if (item == expandedItem) {
+ mExpandedIndex = i;
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
index 5c08412..3692d96 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
@@ -30,7 +30,10 @@
public class StatusBar extends CustomBar {
- public StatusBar(Context context, Density density) throws XmlPullParserException {
+ public StatusBar(Context context, Density density, int direction, boolean RtlEnabled)
+ throws XmlPullParserException {
+ // FIXME: if direction is RTL but it's not enabled in application manifest, mirror this bar.
+
super(context, density, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml");
// FIXME: use FILL_H?
@@ -42,10 +45,7 @@
// We do know the order though.
// 0 is the spacer
loadIcon(1, "stat_sys_wifi_signal_4_fully.png", density);
- Drawable drawable = loadIcon(2, ResourceType.DRAWABLE, "stat_sys_battery_charge");
- if (drawable instanceof LevelListDrawable) {
- ((LevelListDrawable) drawable).setLevel(100);
- }
+ loadIcon(2, "stat_sys_battery_charge_anim100.png", density);
}
@Override
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
index 081ce67..cc7338a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
@@ -52,10 +52,14 @@
private static final String NODE_NAME = "name";
private static final String NODE_FILE = "file";
+ private static final String ATTRIBUTE_VARIANT = "variant";
+ private static final String ATTRIBUTE_VALUE_ELEGANT = "elegant";
private static final String FONT_SUFFIX_NONE = ".ttf";
private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf";
private static final String FONT_SUFFIX_BOLD = "-Bold.ttf";
- private static final String FONT_SUFFIX_ITALIC = "-Italic.ttf";
+ // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked
+ // separately.
+ private static final String FONT_SUFFIX_ITALIC = "Italic.ttf";
private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf";
// This must match the values of Typeface styles so that we can use them for indices in this
@@ -189,6 +193,7 @@
private FontInfo mFontInfo = null;
private final StringBuilder mBuilder = new StringBuilder();
private List<FontInfo> mFontList = new ArrayList<FontInfo>();
+ private boolean isCompactFont = true;
private FontHandler(String osFontsLocation) {
super();
@@ -209,8 +214,21 @@
mFontList = new ArrayList<FontInfo>();
} else if (NODE_FAMILY.equals(localName)) {
if (mFontList != null) {
+ mFontInfo = null;
+ }
+ } else if (NODE_NAME.equals(localName)) {
+ if (mFontList != null && mFontInfo == null) {
mFontInfo = new FontInfo();
}
+ } else if (NODE_FILE.equals(localName)) {
+ if (mFontList != null && mFontInfo == null) {
+ mFontInfo = new FontInfo();
+ }
+ if (ATTRIBUTE_VALUE_ELEGANT.equals(attributes.getValue(ATTRIBUTE_VARIANT))) {
+ isCompactFont = false;
+ } else {
+ isCompactFont = true;
+ }
}
mBuilder.setLength(0);
@@ -223,7 +241,9 @@
*/
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
- mBuilder.append(ch, start, length);
+ if (isCompactFont) {
+ mBuilder.append(ch, start, length);
+ }
}
/* (non-Javadoc)
@@ -259,7 +279,7 @@
}
} else if (NODE_FILE.equals(localName)) {
// handle a new file for an existing Font Info
- if (mFontInfo != null) {
+ if (isCompactFont && mFontInfo != null) {
String fileName = trimXmlWhitespaces(mBuilder.toString());
Font font = getFont(fileName);
if (font != null) {
@@ -267,10 +287,10 @@
mFontInfo.font[Typeface.NORMAL] = font;
} else if (fileName.endsWith(FONT_SUFFIX_BOLD)) {
mFontInfo.font[Typeface.BOLD] = font;
- } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) {
- mFontInfo.font[Typeface.ITALIC] = font;
} else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) {
mFontInfo.font[Typeface.BOLD_ITALIC] = font;
+ } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) {
+ mFontInfo.font[Typeface.ITALIC] = font;
} else if (fileName.endsWith(FONT_SUFFIX_NONE)) {
mFontInfo.font[Typeface.NORMAL] = font;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index d0afc07..ced1387 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -121,7 +121,8 @@
// build the context
mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
- mParams.getProjectCallback(), getConfiguration(), mParams.getTargetSdkVersion());
+ mParams.getProjectCallback(), getConfiguration(), mParams.getTargetSdkVersion(),
+ mParams.isRtlSupported());
setUp();
@@ -247,11 +248,16 @@
* The counterpart is {@link #setUp()}.
*/
private void tearDown() {
- // Make sure to remove static references, otherwise we could not unload the lib
- mContext.disposeResources();
+ // The context may be null, if there was an error during init().
+ if (mContext != null) {
+ // Make sure to remove static references, otherwise we could not unload the lib
+ mContext.disposeResources();
+ }
- // quit HandlerThread created during this session.
- HandlerThread_Delegate.cleanUp(sCurrentContext);
+ if (sCurrentContext != null) {
+ // quit HandlerThread created during this session.
+ HandlerThread_Delegate.cleanUp(sCurrentContext);
+ }
// clear the stored ViewConfiguration since the map is per density and not per context.
ViewConfiguration_Accessor.clearConfigurations();
@@ -262,8 +268,11 @@
sCurrentContext = null;
Bridge.setLog(null);
- mContext.getRenderResources().setFrameworkResourceIdProvider(null);
- mContext.getRenderResources().setLogger(null);
+ if (mContext != null) {
+ mContext.getRenderResources().setFrameworkResourceIdProvider(null);
+ mContext.getRenderResources().setLogger(null);
+ }
+
}
public static BridgeContext getCurrentContext() {
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 c14af4a..0af14dd 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
@@ -28,7 +28,6 @@
import com.android.ide.common.rendering.api.IAnimationListener;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.IProjectCallback;
-import com.android.ide.common.rendering.api.RenderParams;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.ResourceReference;
@@ -39,16 +38,23 @@
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.internal.util.XmlUtils;
+import com.android.internal.view.menu.ActionMenuItemView;
+import com.android.internal.view.menu.BridgeMenuItemImpl;
+import com.android.internal.view.menu.IconMenuItemView;
+import com.android.internal.view.menu.ListMenuItemView;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuView;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.bars.FakeActionBar;
import com.android.layoutlib.bridge.bars.NavigationBar;
import com.android.layoutlib.bridge.bars.StatusBar;
import com.android.layoutlib.bridge.bars.TitleBar;
+import com.android.layoutlib.bridge.bars.ActionBarLayout;
import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
+import com.android.resources.Density;
import com.android.resources.ResourceType;
import com.android.resources.ScreenOrientation;
import com.android.util.Pair;
@@ -100,11 +106,10 @@
/**
* Class implementing the render session.
- *
+ * <p/>
* A session is a stateful representation of a layout file. It is initialized with data coming
* through the {@link Bridge} API to inflate the layout. Further actions and rendering can then
* be done on the layout.
- *
*/
public class RenderSessionImpl extends RenderAction<SessionParams> {
@@ -134,6 +139,7 @@
// information being returned through the API
private BufferedImage mImage;
private List<ViewInfo> mViewInfoList;
+ private List<ViewInfo> mSystemViewInfoList;
private static final class PostInflateException extends Exception {
private static final long serialVersionUID = 1L;
@@ -146,10 +152,11 @@
/**
* Creates a layout scene with all the information coming from the layout bridge API.
* <p>
- * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init()}, which act as a
+ * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init(long)},
+ * which act as a
* call to {@link RenderSessionImpl#acquire(long)}
*
- * @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams)
+ * @see Bridge#createSession(SessionParams)
*/
public RenderSessionImpl(SessionParams params) {
super(new SessionParams(params));
@@ -169,7 +176,7 @@
@Override
public Result init(long timeout) {
Result result = super.init(timeout);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
return result;
}
@@ -194,6 +201,7 @@
// FIXME: find those out, and possibly add them to the render params
boolean hasSystemNavBar = true;
boolean hasNavigationBar = true;
+ //noinspection ConstantConditions
IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
metrics, Surface.ROTATION_0,
hasSystemNavBar, hasNavigationBar);
@@ -225,13 +233,15 @@
SessionParams params = getParams();
HardwareConfig hardwareConfig = params.getHardwareConfig();
BridgeContext context = getContext();
-
+ boolean isRtl = Bridge.isLocaleRtl(params.getLocale());
+ int layoutDirection = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
// the view group that receives the window background.
- ViewGroup backgroundView = null;
+ ViewGroup backgroundView;
if (mWindowIsFloating || params.isForceNoDecor()) {
backgroundView = mViewRoot = mContentRoot = new FrameLayout(context);
+ mViewRoot.setLayoutDirection(layoutDirection);
} else {
if (hasSoftwareButtons() && mNavigationBarOrientation == LinearLayout.VERTICAL) {
/*
@@ -253,18 +263,15 @@
the bottom
*/
LinearLayout topLayout = new LinearLayout(context);
+ topLayout.setLayoutDirection(layoutDirection);
mViewRoot = topLayout;
topLayout.setOrientation(LinearLayout.HORIZONTAL);
try {
- NavigationBar navigationBar = new NavigationBar(context,
- hardwareConfig.getDensity(), LinearLayout.VERTICAL);
- navigationBar.setLayoutParams(
- new LinearLayout.LayoutParams(
- mNavigationBarSize,
- LayoutParams.MATCH_PARENT));
+ NavigationBar navigationBar = createNavigationBar(context,
+ hardwareConfig.getDensity(), isRtl, params.isRtlSupported());
topLayout.addView(navigationBar);
- } catch (XmlPullParserException e) {
+ } catch (XmlPullParserException ignored) {
}
}
@@ -290,29 +297,37 @@
LinearLayout topLayout = new LinearLayout(context);
topLayout.setOrientation(LinearLayout.VERTICAL);
+ topLayout.setLayoutDirection(layoutDirection);
// if we don't already have a view root this is it
if (mViewRoot == null) {
mViewRoot = topLayout;
} else {
+ int topLayoutWidth =
+ params.getHardwareConfig().getScreenWidth() - mNavigationBarSize;
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
- LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
- layoutParams.weight = 1;
+ topLayoutWidth, LayoutParams.MATCH_PARENT);
topLayout.setLayoutParams(layoutParams);
// this is the case of soft buttons + vertical bar.
// this top layout is the first layout in the horizontal layout. see above)
- mViewRoot.addView(topLayout, 0);
+ if (isRtl && params.isRtlSupported()) {
+ // If RTL is enabled, layoutlib will mirror the layouts. So, add the
+ // topLayout to the right of Navigation Bar and layoutlib will draw it
+ // to the left.
+ mViewRoot.addView(topLayout);
+ } else {
+ // Add the top layout to the left of the Navigation Bar.
+ mViewRoot.addView(topLayout, 0);
+ }
}
if (mStatusBarSize > 0) {
// system bar
try {
- StatusBar systemBar = new StatusBar(context, hardwareConfig.getDensity());
- systemBar.setLayoutParams(
- new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, mStatusBarSize));
- topLayout.addView(systemBar);
- } catch (XmlPullParserException e) {
+ StatusBar statusBar = createStatusBar(context, hardwareConfig.getDensity(),
+ layoutDirection, params.isRtlSupported());
+ topLayout.addView(statusBar);
+ } catch (XmlPullParserException ignored) {
}
}
@@ -321,7 +336,7 @@
backgroundView = backgroundLayout;
backgroundLayout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ LayoutParams.MATCH_PARENT, 0);
layoutParams.weight = 1;
backgroundLayout.setLayoutParams(layoutParams);
topLayout.addView(backgroundLayout);
@@ -329,49 +344,38 @@
// if the theme says no title/action bar, then the size will be 0
if (mActionBarSize > 0) {
- try {
- FakeActionBar actionBar = new FakeActionBar(context,
- hardwareConfig.getDensity(),
- params.getAppLabel(), params.getAppIcon());
- actionBar.setLayoutParams(
- new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, mActionBarSize));
- backgroundLayout.addView(actionBar);
- } catch (XmlPullParserException e) {
-
- }
+ ActionBarLayout actionBar = createActionBar(context, params);
+ backgroundLayout.addView(actionBar);
+ actionBar.createMenuPopup();
+ mContentRoot = actionBar.getContentRoot();
} else if (mTitleBarSize > 0) {
try {
- TitleBar titleBar = new TitleBar(context,
+ TitleBar titleBar = createTitleBar(context,
hardwareConfig.getDensity(), params.getAppLabel());
- titleBar.setLayoutParams(
- new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, mTitleBarSize));
backgroundLayout.addView(titleBar);
- } catch (XmlPullParserException e) {
+ } catch (XmlPullParserException ignored) {
}
}
// content frame
- mContentRoot = new FrameLayout(context);
- layoutParams = new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
- layoutParams.weight = 1;
- mContentRoot.setLayoutParams(layoutParams);
- backgroundLayout.addView(mContentRoot);
+ if (mContentRoot == null) {
+ mContentRoot = new FrameLayout(context);
+ layoutParams = new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, 0);
+ layoutParams.weight = 1;
+ mContentRoot.setLayoutParams(layoutParams);
+ backgroundLayout.addView(mContentRoot);
+ }
if (mNavigationBarOrientation == LinearLayout.HORIZONTAL &&
mNavigationBarSize > 0) {
// system bar
try {
- NavigationBar navigationBar = new NavigationBar(context,
- hardwareConfig.getDensity(), LinearLayout.HORIZONTAL);
- navigationBar.setLayoutParams(
- new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, mNavigationBarSize));
+ NavigationBar navigationBar = createNavigationBar(context,
+ hardwareConfig.getDensity(), isRtl, params.isRtlSupported());
topLayout.addView(navigationBar);
- } catch (XmlPullParserException e) {
+ } catch (XmlPullParserException ignored) {
}
}
@@ -396,7 +400,7 @@
postInflateProcess(view, params.getProjectCallback());
// get the background drawable
- if (mWindowBackground != null && backgroundView != null) {
+ if (mWindowBackground != null) {
Drawable d = ResourceHelper.getDrawable(mWindowBackground, context);
backgroundView.setBackground(d);
}
@@ -427,7 +431,7 @@
* @throws IllegalStateException if the current context is different than the one owned by
* the scene, or if {@link #acquire(long)} was not called.
*
- * @see RenderParams#getRenderingMode()
+ * @see SessionParams#getRenderingMode()
* @see RenderSession#render(long)
*/
public Result render(boolean freshRender) {
@@ -473,6 +477,7 @@
// first measure the full layout, with EXACTLY to get the size of the
// content as it is inside the decor/dialog
+ @SuppressWarnings("deprecation")
Pair<Integer, Integer> exactMeasure = measureView(
mViewRoot, mContentRoot.getChildAt(0),
mMeasuredScreenWidth, MeasureSpec.EXACTLY,
@@ -480,6 +485,7 @@
// now measure the content only using UNSPECIFIED (where applicable, based on
// the rendering mode). This will give us the size the content needs.
+ @SuppressWarnings("deprecation")
Pair<Integer, Integer> result = measureView(
mContentRoot, mContentRoot.getChildAt(0),
mMeasuredScreenWidth, widthMeasureSpecMode,
@@ -555,7 +561,7 @@
mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
}
- if (freshRender && newImage == false) {
+ if (freshRender && !newImage) {
Graphics2D gc = mImage.createGraphics();
gc.setComposite(AlphaComposite.Src);
@@ -570,7 +576,8 @@
mViewRoot.draw(mCanvas);
}
- mViewInfoList = startVisitingViews(mViewRoot, 0, params.getExtendedViewInfoMode());
+ mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
+ false);
// success!
return SUCCESS.createResult();
@@ -600,6 +607,7 @@
* @param heightMode the MeasureSpec mode to use for the height.
* @return the measured width/height if measuredView is non-null, null otherwise.
*/
+ @SuppressWarnings("deprecation") // For the use of Pair
private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
int width, int widthMode, int height, int heightMode) {
int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
@@ -630,7 +638,7 @@
BridgeContext context = getContext();
// find the animation file.
- ResourceValue animationResource = null;
+ ResourceValue animationResource;
int animationId = 0;
if (isFrameworkAnimation) {
animationResource = context.getRenderResources().getFrameworkResource(
@@ -720,7 +728,7 @@
// add it to the parentView in the correct location
Result result = addView(parentView, child, index);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
return result;
}
@@ -790,13 +798,13 @@
public void run() {
Result result = moveView(previousParent, newParentView, childView, index,
params);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
listener.done(result);
}
// ready to do the work, acquire the scene.
result = acquire(250);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
listener.done(result);
return;
}
@@ -854,7 +862,7 @@
}
Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
return result;
}
@@ -988,7 +996,7 @@
}
Result result = removeView(parent, childView);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
return result;
}
@@ -1016,7 +1024,7 @@
private void findBackground(RenderResources resources) {
- if (getParams().isBgColorOverridden() == false) {
+ if (!getParams().isBgColorOverridden()) {
mWindowBackground = resources.findItemInTheme("windowBackground",
true /*isFrameworkAttr*/);
if (mWindowBackground != null) {
@@ -1033,7 +1041,7 @@
boolean windowFullscreen = getBooleanThemeValue(resources,
"windowFullscreen", false /*defaultValue*/);
- if (windowFullscreen == false && mWindowIsFloating == false) {
+ if (!windowFullscreen && !mWindowIsFloating) {
// default value
mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT;
@@ -1087,7 +1095,7 @@
boolean windowNoTitle = getBooleanThemeValue(resources,
"windowNoTitle", false /*defaultValue*/);
- if (windowNoTitle == false) {
+ if (!windowNoTitle) {
// default size of the window title bar
mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT;
@@ -1114,7 +1122,7 @@
}
private void findNavigationBar(RenderResources resources, DisplayMetrics metrics) {
- if (hasSoftwareButtons() && mWindowIsFloating == false) {
+ if (hasSoftwareButtons() && !mWindowIsFloating) {
// default value
mNavigationBarSize = 48; // ??
@@ -1128,15 +1136,12 @@
int shortSize = hardwareConfig.getScreenHeight();
// compute in dp
- int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / hardwareConfig.getDensity().getDpiValue();
+ int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT /
+ hardwareConfig.getDensity().getDpiValue();
- if (shortSizeDp < 600) {
- // 0-599dp: "phone" UI with bar on the side
- barOnBottom = false;
- } else {
- // 600+dp: "tablet" UI with bar on the bottom
- barOnBottom = true;
- }
+ // 0-599dp: "phone" UI with bar on the side
+ // 600+dp: "tablet" UI with bar on the bottom
+ barOnBottom = shortSizeDp >= 600;
}
if (barOnBottom) {
@@ -1187,13 +1192,15 @@
}
/**
- * Post process on a view hierachy that was just inflated.
- * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the
+ * Post process on a view hierarchy that was just inflated.
+ * <p/>
+ * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the
* {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
* based on the content of the {@link FrameLayout}.
* @param view the root view to process.
* @param projectCallback callback to the project.
*/
+ @SuppressWarnings("deprecation") // For the use of Pair
private void postInflateProcess(View view, IProjectCallback projectCallback)
throws PostInflateException {
if (view instanceof TabHost) {
@@ -1296,7 +1303,7 @@
"TabHost requires a TabWidget with id \"android:id/tabs\".\n");
}
- if ((v instanceof TabWidget) == false) {
+ if (!(v instanceof TabWidget)) {
throw new PostInflateException(String.format(
"TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
"View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
@@ -1305,12 +1312,14 @@
v = tabHost.findViewById(android.R.id.tabcontent);
if (v == null) {
- // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty)
+ // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty)
+ //noinspection SpellCheckingInspection
throw new PostInflateException(
"TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
}
- if ((v instanceof FrameLayout) == false) {
+ if (!(v instanceof FrameLayout)) {
+ //noinspection SpellCheckingInspection
throw new PostInflateException(String.format(
"TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
"View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
@@ -1318,7 +1327,7 @@
FrameLayout content = (FrameLayout)v;
- // now process the content of the framelayout and dynamically create tabs for it.
+ // now process the content of the frameLayout and dynamically create tabs for it.
final int count = content.getChildCount();
// this must be called before addTab() so that the TabHost searches its TabWidget
@@ -1336,13 +1345,13 @@
}
});
tabHost.addTab(spec);
- return;
} else {
- // for each child of the framelayout, add a new TabSpec
+ // for each child of the frameLayout, add a new TabSpec
for (int i = 0 ; i < count ; i++) {
View child = content.getChildAt(i);
String tabSpec = String.format("tab_spec%d", i+1);
int id = child.getId();
+ @SuppressWarnings("deprecation")
Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id);
String name;
if (resource != null) {
@@ -1355,50 +1364,126 @@
}
}
- private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) {
- if (view == null) {
- return null;
- }
-
- // adjust the offset to this view.
- offset += view.getTop();
-
- if (view == mContentRoot) {
- return visitAllChildren(mContentRoot, offset, setExtendedInfo);
- }
-
- // otherwise, look for mContentRoot in the children
- if (view instanceof ViewGroup) {
- ViewGroup group = ((ViewGroup) view);
-
- for (int i = 0; i < group.getChildCount(); i++) {
- List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset,
- setExtendedInfo);
- if (list != null) {
- return list;
- }
- }
- }
-
- return null;
- }
-
/**
- * Visits a View and its children and generate a {@link ViewInfo} containing the
+ * Visits a {@link View} and its children and generate a {@link ViewInfo} containing the
* bounds of all the views.
+ *
* @param view the root View
* @param offset an offset for the view bounds.
* @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
+ * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
+ * content frame.
+ *
+ * @return {@code ViewInfo} containing the bounds of the view and it children otherwise.
*/
- private ViewInfo visit(View view, int offset, boolean setExtendedInfo) {
+ private ViewInfo visit(View view, int offset, boolean setExtendedInfo,
+ boolean isContentFrame) {
+ ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame);
+
+ if (view instanceof ViewGroup) {
+ ViewGroup group = ((ViewGroup) view);
+ result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset,
+ setExtendedInfo, isContentFrame));
+ }
+ return result;
+ }
+
+ /**
+ * Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo}
+ * containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with
+ * the children of the {@code mContentRoot}.
+ *
+ * @param viewGroup the root View
+ * @param offset an offset from the top for the content view frame.
+ * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
+ * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
+ * content frame. {@code false} if the {@code ViewInfo} to be created is
+ * part of the system decor.
+ */
+ private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
+ boolean setExtendedInfo, boolean isContentFrame) {
+ if (viewGroup == null) {
+ return null;
+ }
+
+ if (!isContentFrame) {
+ offset += viewGroup.getTop();
+ }
+
+ int childCount = viewGroup.getChildCount();
+ if (viewGroup == mContentRoot) {
+ List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
+ List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
+ for (int i = 0; i < childCount; i++) {
+ ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset,
+ setExtendedInfo);
+ childrenWithoutOffset.add(childViewInfo[0]);
+ childrenWithOffset.add(childViewInfo[1]);
+ }
+ mViewInfoList = childrenWithOffset;
+ return childrenWithoutOffset;
+ } else {
+ List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
+ for (int i = 0; i < childCount; i++) {
+ children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo,
+ isContentFrame));
+ }
+ return children;
+ }
+ }
+
+ /**
+ * Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the
+ * bounds of all the views. It returns two {@code ViewInfo} objects with the same children,
+ * one with the {@code offset} and other without the {@code offset}. The offset is needed to
+ * get the right bounds if the {@code ViewInfo} hierarchy is accessed from
+ * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the
+ * offset is not needed.
+ *
+ * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at
+ * index 1 is with the offset.
+ */
+ private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) {
+ ViewInfo[] result = new ViewInfo[2];
+ if (view == null) {
+ return result;
+ }
+
+ result[0] = createViewInfo(view, 0, setExtendedInfo, true);
+ result[1] = createViewInfo(view, offset, setExtendedInfo, true);
+ if (view instanceof ViewGroup) {
+ List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true);
+ result[0].setChildren(children);
+ result[1].setChildren(children);
+ }
+ return result;
+ }
+
+ /**
+ * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children
+ * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not
+ * set.
+ * @param offset an offset for the view bounds. Used only if view is part of the content frame.
+ */
+ private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo,
+ boolean isContentFrame) {
if (view == null) {
return null;
}
- ViewInfo result = new ViewInfo(view.getClass().getName(),
- getContext().getViewKey(view),
- view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset,
- view, view.getLayoutParams());
+ ViewInfo result;
+ if (isContentFrame) {
+ result = new ViewInfo(view.getClass().getName(),
+ getViewKey(view),
+ view.getLeft(), view.getTop() + offset, view.getRight(),
+ view.getBottom() + offset, view, view.getLayoutParams());
+
+ } else {
+ result = new SystemViewInfo(view.getClass().getName(),
+ getViewKey(view),
+ view.getLeft(), view.getTop(), view.getRight(),
+ view.getBottom(), view, view.getLayoutParams());
+ }
if (setExtendedInfo) {
MarginLayoutParams marginParams = null;
@@ -1413,39 +1498,92 @@
marginParams != null ? marginParams.bottomMargin : 0);
}
- if (view instanceof ViewGroup) {
- ViewGroup group = ((ViewGroup) view);
- result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo));
- }
-
return result;
}
/**
- * Visits all the children of a given ViewGroup generate a list of {@link ViewInfo}
- * containing the bounds of all the views.
- * @param view the root View
- * @param offset an offset for the view bounds.
- * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
+ * The cookie for menu items are stored in menu item and not in the map from View stored in
+ * BridgeContext.
*/
- private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
- boolean setExtendedInfo) {
- if (viewGroup == null) {
- return null;
+ private Object getViewKey(View view) {
+ BridgeContext context = getContext();
+ if (!(view instanceof MenuView.ItemView)) {
+ return context.getViewKey(view);
+ }
+ MenuItemImpl menuItem;
+ if (view instanceof ActionMenuItemView) {
+ menuItem = ((ActionMenuItemView) view).getItemData();
+ } else if (view instanceof ListMenuItemView) {
+ menuItem = ((ListMenuItemView) view).getItemData();
+ } else if (view instanceof IconMenuItemView) {
+ menuItem = ((IconMenuItemView) view).getItemData();
+ } else {
+ menuItem = null;
+ }
+ if (menuItem instanceof BridgeMenuItemImpl) {
+ return ((BridgeMenuItemImpl) menuItem).getViewCookie();
}
- List<ViewInfo> children = new ArrayList<ViewInfo>();
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo));
- }
- return children;
+ return null;
}
-
private void invalidateRenderingSize() {
mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
}
+ /**
+ * Creates the status bar with wifi and battery icons.
+ */
+ private StatusBar createStatusBar(BridgeContext context, Density density, int direction,
+ boolean isRtlSupported) throws XmlPullParserException {
+ StatusBar statusBar = new StatusBar(context, density,
+ direction, isRtlSupported);
+ statusBar.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, mStatusBarSize));
+ return statusBar;
+ }
+
+ /**
+ * Creates the navigation bar with back, home and recent buttons.
+ *
+ * @param isRtl true if the current locale is right-to-left
+ * @param isRtlSupported true is the project manifest declares that the application
+ * is RTL aware.
+ */
+ private NavigationBar createNavigationBar(BridgeContext context, Density density,
+ boolean isRtl, boolean isRtlSupported) throws XmlPullParserException {
+ NavigationBar navigationBar = new NavigationBar(context,
+ density, mNavigationBarOrientation, isRtl,
+ isRtlSupported);
+ if (mNavigationBarOrientation == LinearLayout.VERTICAL) {
+ navigationBar.setLayoutParams(new LinearLayout.LayoutParams(mNavigationBarSize,
+ LayoutParams.MATCH_PARENT));
+ } else {
+ navigationBar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ mNavigationBarSize));
+ }
+ return navigationBar;
+ }
+
+ private TitleBar createTitleBar(BridgeContext context, Density density, String title)
+ throws XmlPullParserException {
+ TitleBar titleBar = new TitleBar(context, density, title);
+ titleBar.setLayoutParams(
+ new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, mTitleBarSize));
+ return titleBar;
+ }
+
+ /**
+ * 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;
+ }
+
public BufferedImage getImage() {
return mImage;
}
@@ -1458,6 +1596,10 @@
return mViewInfoList;
}
+ public List<ViewInfo> getSystemViewInfos() {
+ return mSystemViewInfoList;
+ }
+
public Map<String, String> getDefaultProperties(Object viewObject) {
return getContext().getDefaultPropMap(viewObject);
}
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 6dcb693..adb0937 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
@@ -165,6 +165,9 @@
* @param context the current context
*/
public static Drawable getDrawable(ResourceValue value, BridgeContext context) {
+ if (value == null) {
+ return null;
+ }
String stringValue = value.getValue();
if (RenderResources.REFERENCE_NULL.equals(stringValue)) {
return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java
new file mode 100644
index 0000000..5c267df
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java
@@ -0,0 +1,38 @@
+/*
+ * 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.impl;
+
+import com.android.ide.common.rendering.api.ViewInfo;
+
+public class SystemViewInfo extends ViewInfo {
+
+ public SystemViewInfo(String name, Object cookie, int left, int top,
+ int right, int bottom) {
+ super(name, cookie, left, top, right, bottom);
+ }
+
+ public SystemViewInfo(String name, Object cookie, int left, int top,
+ int right, int bottom, Object viewObject, Object layoutParamsObject) {
+ super(name, cookie, left, top, right, bottom, viewObject,
+ layoutParamsObject);
+ }
+
+ @Override
+ public boolean isSystemView() {
+ return true;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
index fb2fc85..fd75f70 100644
--- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
+++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
@@ -171,12 +171,18 @@
result.longStandAloneMonthNames = result.longMonthNames;
result.shortStandAloneMonthNames = result.shortMonthNames;
+ // The platform code expects this to begin at index 1, rather than 0. It maps it directly to
+ // the constants from java.util.Calendar.<weekday>
result.longWeekdayNames = new String[] {
- "Monday" ,"Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
+ "", "Sunday", "Monday" ,"Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
result.shortWeekdayNames = new String[] {
- "Mon" ,"Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
+ "", "Sun", "Mon" ,"Tue", "Wed", "Thu", "Fri", "Sat" };
+ result.tinyWeekdayNames = new String[] {
+ "", "S", "M", "T", "W", "T", "F", "S" };
+
result.longStandAloneWeekdayNames = result.longWeekdayNames;
result.shortStandAloneWeekdayNames = result.shortWeekdayNames;
+ result.tinyStandAloneWeekdayNames = result.tinyWeekdayNames;
result.fullTimeFormat = "";
result.longTimeFormat = "";
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
index d3218db..7c0bc84 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
@@ -129,6 +129,15 @@
originalClass.getName()),
delegateMethod.getAnnotation(LayoutlibDelegate.class));
+ // check the return type of the methods match.
+ assertTrue(
+ String.format("Delegate method %1$s.%2$s does not match the corresponding " +
+ "framework method which returns %3$s",
+ delegateClass.getName(),
+ getMethodName(delegateMethod),
+ originalMethod.getReturnType().getName()),
+ delegateMethod.getReturnType() == originalMethod.getReturnType());
+
// check that the method is static
assertTrue(
String.format(
diff --git a/tools/layoutlib/create/.classpath b/tools/layoutlib/create/.classpath
index dbc4cfd..cd8bb0d 100644
--- a/tools/layoutlib/create/.classpath
+++ b/tools/layoutlib/create/.classpath
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
- <classpathentry excluding="mock_android/" kind="src" path="tests"/>
+ <classpathentry excluding="mock_data/" kind="src" path="tests"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_PLAT/prebuilts/tools/common/asm-tools/src-4.0.zip"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index 894611b..e490ac5 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -71,6 +71,9 @@
and "." and "$" are interpreted as-is).
In practice we almost but not quite request the inclusion of full packages.
+The analyzer is also given a list of classes to exclude. A fake implementation of these
+classes is injected by the Generator.
+
With this information, the analyzer parses the input zip to find all the classes.
All classes deriving from the requested bases classes are kept.
All classes which name matched the glob pattern are kept.
@@ -93,6 +96,7 @@
- specific methods for which to delegate calls.
- specific methods to remove based on their return type.
- specific classes to rename.
+- specific classes to refactor.
Each of these are specific strategies we use to be able to modify the Android code
to fit within the Eclipse renderer. These strategies are explained beow.
@@ -100,10 +104,7 @@
The core method of the generator is transform(): it takes an input ASM ClassReader
and modifies it to produce a byte array suitable for the final JAR file.
-The first step of the transformation is changing the name of the class in case
-we requested the class to be renamed. This uses the RenameClassAdapter to also rename
-all inner classes and references in methods and types. Note that other classes are
-not transformed and keep referencing the original name.
+The first step of the transformation is to implement the method delegates.
The TransformClassAdapter is then used to process the potentially renamed class.
All protected or private classes are market as public.
@@ -115,11 +116,25 @@
The code of the methods is then kept as-is, except for native methods which are
replaced by a stub. Methods that are to be overridden are also replaced by a stub.
-The transformed class is then fed through the DelegateClassAdapter to implement
-method delegates.
-
Finally fields are also visited and changed from protected/private to public.
+The next step of the transformation is changing the name of the class in case
+we requested the class to be renamed. This uses the RenameClassAdapter to also rename
+all inner classes and references in methods and types. Note that other classes are
+not transformed and keep referencing the original name.
+
+The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but
+updates the references in all classes. This is used to update the references of classes
+in the java package that were added in the Dalvik VM but are not a part of the standard
+JVM. The existing classes are modified to update all references to these non-standard
+classes. An alternate implementation of these (com.android.tools.layoutlib.java.*) is
+injected.
+
+The ClassAdapters are chained together to achieve the desired output. (Look at section
+2.2.7 Transformation chains in the asm user guide, link in the References.) The order of
+execution of these is:
+ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] ->
+RefactorClassAdapter -> ClassWriter
- Method stubs
--------------
@@ -141,19 +156,28 @@
- Strategies
------------
-We currently have 4 strategies to deal with overriding the rendering code
+We currently have 6 strategies to deal with overriding the rendering code
and make it run in Eclipse. Most of these strategies are implemented hand-in-hand
by the bridge (which runs in Eclipse) and the generator.
1- Class Injection
-This is the easiest: we currently inject 4 classes, namely:
+This is the easiest: we currently inject the following classes:
- OverrideMethod and its associated MethodListener and MethodAdapter are used
to intercept calls to some specific methods that are stubbed out and change
their return value.
- CreateInfo class, which configured the generator. Not used yet, but could
in theory help us track what the generator changed.
+- AutoCloseable is part of Java 7. To enable us to still run on Java 6, a new class is
+ injected. The implementation for the class has been taken from Android's libcore
+ (platform/libcore/luni/src/main/java/java/lang/AutoCloseable.java).
+- Charsets, ModifiedUtf8, IntegralToString and UnsafeByteSequence are not part of the
+ standard JAVA VM.
+ They are added to the Dalvik VM for performance reasons. An implementation that is very
+ close to the original (which is at platform/libcore/luni/src/main/java/...) is injected.
+ Since these classees were in part of the java package, where we can't inject classes,
+ all references to these have been updated (See strategy 4- Refactoring Classes).
2- Overriding methods
@@ -189,7 +213,15 @@
This won't rename/replace the inner static methods of a given class.
-4- Method erasure based on return type
+4- Refactoring classes
+
+This is very similar to the Renaming classes except that it also updates the reference in
+all classes. This is done for classes which are added to the Dalvik VM for performance
+reasons but are not present in the Standard Java VM. An implementation for these classes
+is also injected.
+
+
+5- Method erasure based on return type
This is mostly an implementation detail of the bridge: in the Paint class
mentioned above, some inner static classes are used to pass around
@@ -201,7 +233,7 @@
bridge will provide its own implementation.
-5- Method Delegates
+6- Method Delegates
This strategy is used to override method implementations.
Given a method SomeClass.MethodName(), 1 or 2 methods are generated:
@@ -233,7 +265,7 @@
http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
ASM user guide:
- http://download.forge.objectweb.org/asm/asm-guide.pdf
+ http://download.forge.objectweb.org/asm/asm4-guide.pdf
--
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
new file mode 100644
index 0000000..b2caa25
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
@@ -0,0 +1,418 @@
+/*
+ * 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 com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+import org.objectweb.asm.signature.SignatureWriter;
+
+/**
+ * Provides the common code for RenameClassAdapter and RefactorClassAdapter. It
+ * goes through the complete class and finds references to other classes. It
+ * then calls {@link #renameInternalType(String)} to convert the className to
+ * the new value, if need be.
+ */
+public abstract class AbstractClassAdapter extends ClassVisitor {
+
+ /**
+ * Returns the new FQCN for the class, if the reference to this class needs
+ * to be updated. Else, it returns the same string.
+ * @param name Old FQCN
+ * @return New FQCN if it needs to be renamed, else the old FQCN
+ */
+ abstract String renameInternalType(String name);
+
+ public AbstractClassAdapter(ClassVisitor cv) {
+ super(Opcodes.ASM4, cv);
+ }
+
+ /**
+ * Renames a type descriptor, e.g. "Lcom.package.MyClass;"
+ * If the type doesn't need to be renamed, returns the input string as-is.
+ */
+ String renameTypeDesc(String desc) {
+ if (desc == null) {
+ return null;
+ }
+
+ return renameType(Type.getType(desc));
+ }
+
+ /**
+ * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
+ * object element, e.g. "[Lcom.package.MyClass;"
+ * If the type doesn't need to be renamed, returns the internal name of the input type.
+ */
+ String renameType(Type type) {
+ if (type == null) {
+ return null;
+ }
+
+ if (type.getSort() == Type.OBJECT) {
+ String in = type.getInternalName();
+ return "L" + renameInternalType(in) + ";";
+ } else if (type.getSort() == Type.ARRAY) {
+ StringBuilder sb = new StringBuilder();
+ for (int n = type.getDimensions(); n > 0; n--) {
+ sb.append('[');
+ }
+ sb.append(renameType(type.getElementType()));
+ return sb.toString();
+ }
+ return type.getDescriptor();
+ }
+
+ /**
+ * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
+ * object element, e.g. "[Lcom.package.MyClass;".
+ * This is like renameType() except that it returns a Type object.
+ * If the type doesn't need to be renamed, returns the input type object.
+ */
+ Type renameTypeAsType(Type type) {
+ if (type == null) {
+ return null;
+ }
+
+ if (type.getSort() == Type.OBJECT) {
+ String in = type.getInternalName();
+ String newIn = renameInternalType(in);
+ if (newIn != in) {
+ return Type.getType("L" + newIn + ";");
+ }
+ } else if (type.getSort() == Type.ARRAY) {
+ StringBuilder sb = new StringBuilder();
+ for (int n = type.getDimensions(); n > 0; n--) {
+ sb.append('[');
+ }
+ sb.append(renameType(type.getElementType()));
+ return Type.getType(sb.toString());
+ }
+ return type;
+ }
+
+ /**
+ * Renames a method descriptor, i.e. applies renameType to all arguments and to the
+ * return value.
+ */
+ String renameMethodDesc(String desc) {
+ if (desc == null) {
+ return null;
+ }
+
+ Type[] args = Type.getArgumentTypes(desc);
+
+ StringBuilder sb = new StringBuilder("(");
+ for (Type arg : args) {
+ String name = renameType(arg);
+ sb.append(name);
+ }
+ sb.append(')');
+
+ Type ret = Type.getReturnType(desc);
+ String name = renameType(ret);
+ sb.append(name);
+
+ return sb.toString();
+ }
+
+
+ /**
+ * Renames the ClassSignature handled by ClassVisitor.visit
+ * or the MethodTypeSignature handled by ClassVisitor.visitMethod.
+ */
+ String renameTypeSignature(String sig) {
+ if (sig == null) {
+ return null;
+ }
+ SignatureReader reader = new SignatureReader(sig);
+ SignatureWriter writer = new SignatureWriter();
+ reader.accept(new RenameSignatureAdapter(writer));
+ sig = writer.toString();
+ return sig;
+ }
+
+
+ /**
+ * Renames the FieldTypeSignature handled by ClassVisitor.visitField
+ * or MethodVisitor.visitLocalVariable.
+ */
+ String renameFieldSignature(String sig) {
+ return renameTypeSignature(sig);
+ }
+
+
+ //----------------------------------
+ // Methods from the ClassAdapter
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ name = renameInternalType(name);
+ superName = renameInternalType(superName);
+ signature = renameTypeSignature(signature);
+ if (interfaces != null) {
+ for (int i = 0; i < interfaces.length; ++i) {
+ interfaces[i] = renameInternalType(interfaces[i]);
+ }
+ }
+
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ name = renameInternalType(name);
+ outerName = renameInternalType(outerName);
+ super.visitInnerClass(name, outerName, innerName, access);
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ super.visitOuterClass(renameInternalType(owner), name, renameTypeDesc(desc));
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ desc = renameMethodDesc(desc);
+ signature = renameTypeSignature(signature);
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+ return new RenameMethodAdapter(mw);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ desc = renameTypeDesc(desc);
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ desc = renameTypeDesc(desc);
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+
+ //----------------------------------
+
+ /**
+ * A method visitor that renames all references from an old class name to a new class name.
+ */
+ public class RenameMethodAdapter extends MethodVisitor {
+
+ /**
+ * Creates a method visitor that renames all references from a given old name to a given new
+ * name. The method visitor will also rename all inner classes.
+ * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
+ */
+ public RenameMethodAdapter(MethodVisitor mv) {
+ super(Opcodes.ASM4, mv);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ desc = renameTypeDesc(desc);
+
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+ desc = renameTypeDesc(desc);
+
+ return super.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ // The type sometimes turns out to be a type descriptor. We try to detect it and fix.
+ if (type.indexOf(';') > 0) {
+ type = renameTypeDesc(type);
+ } else {
+ type = renameInternalType(type);
+ }
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ owner = renameInternalType(owner);
+ desc = renameTypeDesc(desc);
+
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ // The owner sometimes turns out to be a type descriptor. We try to detect it and fix.
+ if (owner.indexOf(';') > 0) {
+ owner = renameTypeDesc(owner);
+ } else {
+ owner = renameInternalType(owner);
+ }
+ desc = renameMethodDesc(desc);
+
+ super.visitMethodInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ // If cst is a Type, this means the code is trying to pull the .class constant
+ // for this class, so it needs to be renamed too.
+ if (cst instanceof Type) {
+ cst = renameTypeAsType((Type) cst);
+ }
+ super.visitLdcInsn(cst);
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ desc = renameTypeDesc(desc);
+
+ super.visitMultiANewArrayInsn(desc, dims);
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ type = renameInternalType(type);
+
+ super.visitTryCatchBlock(start, end, handler, type);
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ desc = renameTypeDesc(desc);
+ signature = renameFieldSignature(signature);
+
+ super.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+
+ }
+
+ //----------------------------------
+
+ public class RenameSignatureAdapter extends SignatureVisitor {
+
+ private final SignatureVisitor mSv;
+
+ public RenameSignatureAdapter(SignatureVisitor sv) {
+ super(Opcodes.ASM4);
+ mSv = sv;
+ }
+
+ @Override
+ public void visitClassType(String name) {
+ name = renameInternalType(name);
+ mSv.visitClassType(name);
+ }
+
+ @Override
+ public void visitInnerClassType(String name) {
+ name = renameInternalType(name);
+ mSv.visitInnerClassType(name);
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ SignatureVisitor sv = mSv.visitArrayType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ mSv.visitBaseType(descriptor);
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ SignatureVisitor sv = mSv.visitClassBound();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitEnd() {
+ mSv.visitEnd();
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ SignatureVisitor sv = mSv.visitExceptionType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ mSv.visitFormalTypeParameter(name);
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ SignatureVisitor sv = mSv.visitInterface();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ SignatureVisitor sv = mSv.visitInterfaceBound();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ SignatureVisitor sv = mSv.visitParameterType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ SignatureVisitor sv = mSv.visitReturnType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ SignatureVisitor sv = mSv.visitSuperclass();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ mSv.visitTypeArgument();
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ SignatureVisitor sv = mSv.visitTypeArgument(wildcard);
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ mSv.visitTypeVariable(name);
+ }
+
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index 412695f..3e75c9e 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -29,11 +29,13 @@
import org.objectweb.asm.signature.SignatureVisitor;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
@@ -57,6 +59,11 @@
private final String[] mDeriveFrom;
/** Glob patterns of classes to keep, e.g. "com.foo.*" */
private final String[] mIncludeGlobs;
+ /** The set of classes to exclude.*/
+ private final Set<String> mExcludedClasses;
+ /** Glob patterns of files to keep as is. */
+ private final String[] mIncludeFileGlobs;
+ /** Copy these files into the output as is. */
/**
* Creates a new analyzer.
@@ -67,14 +74,19 @@
* @param deriveFrom Keep all classes that derive from these one (these included).
* @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*"
* ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
+ * @param includeFileGlobs Glob patterns of files which are kept as is. This is only for files
+ * not ending in .class.
*/
public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
- String[] deriveFrom, String[] includeGlobs) {
+ String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses,
+ String[] includeFileGlobs) {
mLog = log;
mGen = gen;
mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
+ mExcludedClasses = excludeClasses;
+ mIncludeFileGlobs = includeFileGlobs != null ? includeFileGlobs : new String[0];
}
/**
@@ -83,9 +95,10 @@
*/
public void analyze() throws IOException, LogAbortException {
- AsmAnalyzer visitor = this;
+ TreeMap<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
+ Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
- Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar);
+ parseZip(mOsSourceJar, zipClasses, filesFound);
mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
mOsSourceJar.size() > 1 ? "s" : "");
@@ -95,15 +108,29 @@
if (mGen != null) {
mGen.setKeep(found);
mGen.setDeps(deps);
+ mGen.setCopyFiles(filesFound);
}
}
/**
- * Parses a JAR file and returns a list of all classes founds using a map
- * class name => ASM ClassReader. Class names are in the form "android.view.View".
+ * Parses a JAR file and adds all the classes found to <code>classes</code>
+ * and all other files to <code>filesFound</code>.
+ *
+ * @param classes The map of class name => ASM ClassReader. Class names are
+ * in the form "android.view.View".
+ * @param fileFound The map of file name => InputStream. The file name is
+ * in the form "android/data/dataFile".
*/
- Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
- TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
+ void parseZip(List<String> jarPathList, Map<String, ClassReader> classes,
+ Map<String, InputStream> filesFound) throws IOException {
+ if (classes == null || filesFound == null) {
+ return;
+ }
+
+ Pattern[] includeFilePatterns = new Pattern[mIncludeFileGlobs.length];
+ for (int i = 0; i < mIncludeFileGlobs.length; ++i) {
+ includeFilePatterns[i] = getPatternFromGlob(mIncludeFileGlobs[i]);
+ }
for (String jarPath : jarPathList) {
ZipFile zip = new ZipFile(jarPath);
@@ -115,11 +142,17 @@
ClassReader cr = new ClassReader(zip.getInputStream(entry));
String className = classReaderToClassName(cr);
classes.put(className, cr);
+ } else {
+ for (int i = 0; i < includeFilePatterns.length; ++i) {
+ if (includeFilePatterns[i].matcher(entry.getName()).matches()) {
+ filesFound.put(entry.getName(), zip.getInputStream(entry));
+ break;
+ }
+ }
}
}
}
- return classes;
}
/**
@@ -201,7 +234,19 @@
*/
void findGlobs(String globPattern, Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inOutFound) throws LogAbortException {
- // transforms the glob pattern in a regexp:
+
+ Pattern regexp = getPatternFromGlob(globPattern);
+
+ for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
+ String class_name = entry.getKey();
+ if (regexp.matcher(class_name).matches()) {
+ findClass(class_name, zipClasses, inOutFound);
+ }
+ }
+ }
+
+ Pattern getPatternFromGlob(String globPattern) {
+ // transforms the glob pattern in a regexp:
// - escape "." with "\."
// - replace "*" by "[^.]*"
// - escape "$" with "\$"
@@ -215,14 +260,7 @@
globPattern = globPattern.replaceAll("@", ".*");
globPattern += "$";
- Pattern regexp = Pattern.compile(globPattern);
-
- for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
- String class_name = entry.getKey();
- if (regexp.matcher(class_name).matches()) {
- findClass(class_name, zipClasses, inOutFound);
- }
- }
+ return Pattern.compile(globPattern);
}
/**
@@ -232,7 +270,7 @@
*/
void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inOutFound) throws LogAbortException {
- ClassReader super_clazz = findClass(super_name, zipClasses, inOutFound);
+ findClass(super_name, zipClasses, inOutFound);
for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
String className = entry.getKey();
@@ -363,11 +401,12 @@
className = internalToBinaryClassName(className);
- // exclude classes that have already been found
+ // exclude classes that have already been found or are marked to be excluded
if (mInKeep.containsKey(className) ||
mOutKeep.containsKey(className) ||
mInDeps.containsKey(className) ||
- mOutDeps.containsKey(className)) {
+ mOutDeps.containsKey(className) ||
+ mExcludedClasses.contains(getBaseName(className))) {
return;
}
@@ -451,6 +490,13 @@
}
}
+ private String getBaseName(String className) {
+ int pos = className.indexOf('$');
+ if (pos > 0) {
+ return className.substring(0, pos);
+ }
+ return className;
+ }
// ---------------------------------------------------
// --- ClassVisitor, FieldVisitor
@@ -586,8 +632,8 @@
// field instruction
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- // name is the field's name.
- considerName(name);
+ // owner is the class that declares the field.
+ considerName(owner);
// desc is the field's descriptor (see Type).
considerDesc(desc);
}
@@ -682,7 +728,7 @@
}
@Override
- public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
// pass -- table switch instruction
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index a9ede26..207d8ae 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -20,6 +20,7 @@
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
+import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -52,6 +53,8 @@
private Map<String, ClassReader> mKeep;
/** All dependencies that must be completely stubbed. */
private Map<String, ClassReader> mDeps;
+ /** All files that are to be copied as-is. */
+ private Map<String, InputStream> mCopyFiles;
/** Counter of number of classes renamed during transform. */
private int mRenameCount;
/** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */
@@ -65,6 +68,9 @@
/** A map { FQCN => set { method names } } of methods to rewrite as delegates.
* The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
private final HashMap<String, Set<String>> mDelegateMethods;
+ /** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN.
+ * map old-FQCN => new-FQCN */
+ private final HashMap<String, String> mRefactorClasses;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
@@ -119,6 +125,17 @@
mClassesNotRenamed.add(oldFqcn);
}
+ // Create a map of classes to be refactored.
+ mRefactorClasses = new HashMap<String, String>();
+ String[] refactorClasses = createInfo.getJavaPkgClasses();
+ n = refactorClasses.length;
+ for (int i = 0; i < n; i += 2) {
+ assert i + 1 < n;
+ String oldFqcn = binaryToInternalClassName(refactorClasses[i]);
+ String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]);
+ mRefactorClasses.put(oldFqcn, newFqcn);;
+ }
+
// create the map of renamed class -> return type of method to delete.
mDeleteReturns = new HashMap<String, Set<String>>();
String[] deleteReturns = createInfo.getDeleteReturns();
@@ -181,6 +198,11 @@
mDeps = deps;
}
+ /** Sets the map of files to output as-is. */
+ public void setCopyFiles(Map<String, InputStream> copyFiles) {
+ mCopyFiles = copyFiles;
+ }
+
/** Gets the map of classes to output as-is, except if they have native methods */
public Map<String, ClassReader> getKeep() {
return mKeep;
@@ -191,6 +213,11 @@
return mDeps;
}
+ /** Gets the map of files to output as-is. */
+ public Map<String, InputStream> getCopyFiles() {
+ return mCopyFiles;
+ }
+
/** Generates the final JAR */
public void generate() throws FileNotFoundException, IOException {
TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
@@ -218,6 +245,15 @@
all.put(name, b);
}
+ for (Entry<String, InputStream> entry : mCopyFiles.entrySet()) {
+ try {
+ byte[] b = inputStreamToByteArray(entry.getValue());
+ all.put(entry.getKey(), b);
+ } catch (IOException e) {
+ // Ignore.
+ }
+
+ }
mLog.info("# deps classes: %d", mDeps.size());
mLog.info("# keep classes: %d", mKeep.size());
mLog.info("# renamed : %d", mRenameCount);
@@ -308,14 +344,14 @@
// original class reader.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
- ClassVisitor rv = cw;
+ ClassVisitor cv = new RefactorClassAdapter(cw, mRefactorClasses);
if (newName != className) {
- rv = new RenameClassAdapter(cw, className, newName);
+ cv = new RenameClassAdapter(cv, className, newName);
}
- ClassVisitor cv = new TransformClassAdapter(mLog, mStubMethods,
+ cv = new TransformClassAdapter(mLog, mStubMethods,
mDeleteReturns.get(className),
- newName, rv,
+ newName, cv,
stubNativesOnly, stubNativesOnly || hasNativeMethods);
Set<String> delegateMethods = mDelegateMethods.get(className);
@@ -367,4 +403,13 @@
return cv.hasNativeMethods();
}
+ private byte[] inputStreamToByteArray(InputStream is) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ byte[] data = new byte[8192]; // 8KB
+ int n;
+ while ((n = is.read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, n);
+ }
+ return buffer.toByteArray();
+ }
}
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 d955040..e9612a3 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
@@ -17,6 +17,11 @@
package com.android.tools.layoutlib.create;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.java.AutoCloseable;
+import com.android.tools.layoutlib.java.ModifiedUtf8;
+import com.android.tools.layoutlib.java.Charsets;
+import com.android.tools.layoutlib.java.IntegralToString;
+import com.android.tools.layoutlib.java.UnsafeByteSequence;
/**
* Describes the work to be done by {@link AsmGenerator}.
@@ -84,6 +89,15 @@
return DELETE_RETURNS;
}
+ /**
+ * Returns the list of classes to refactor, must be an even list: the binary FQCN of class to
+ * replace followed by the new FQCN. All references to the old class should be updated to the
+ * new class. The list can be empty but must not be null.
+ */
+ @Override
+ public String[] getJavaPkgClasses() {
+ return JAVA_PKG_CLASSES;
+ }
//-----
/**
@@ -95,7 +109,13 @@
MethodAdapter.class,
ICreateInfo.class,
CreateInfo.class,
- LayoutlibDelegate.class
+ LayoutlibDelegate.class,
+ /* Java package classes */
+ AutoCloseable.class,
+ IntegralToString.class,
+ UnsafeByteSequence.class,
+ ModifiedUtf8.class,
+ Charsets.class,
};
/**
@@ -105,6 +125,9 @@
"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.AssetManager#newTheme",
+ "android.content.res.AssetManager#deleteTheme",
+ "android.content.res.AssetManager#applyThemeStyle",
"android.content.res.TypedArray#getValueAt",
"android.graphics.BitmapFactory#finishDecode",
"android.os.Handler#sendMessageAtTime",
@@ -119,6 +142,8 @@
"android.view.ViewRootImpl#isInTouchMode",
"android.view.WindowManagerGlobal#getWindowManagerService",
"android.view.inputmethod.InputMethodManager#getInstance",
+ "android.view.MenuInflater#registerMenu",
+ "com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
"com.android.internal.util.XmlUtils#convertValueToInt",
"com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
};
@@ -167,6 +192,7 @@
"android.graphics.Xfermode",
"android.os.SystemClock",
"android.text.AndroidBidi",
+ "android.text.format.Time",
"android.util.FloatMath",
"android.view.Display",
"libcore.icu.ICU",
@@ -195,6 +221,20 @@
};
/**
+ * The list of class references to update, must be an even list: the binary
+ * FQCN of class to replace followed by the new FQCN. The classes to
+ * replace are to be excluded from the output.
+ */
+ private final static String[] JAVA_PKG_CLASSES =
+ new String[] {
+ "java.lang.AutoCloseable", "com.android.tools.layoutlib.java.AutoCloseable",
+ "java.nio.charset.ModifiedUtf8", "com.android.tools.layoutlib.java.ModifiedUtf8",
+ "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",
+ };
+
+ /**
* List of classes for which the methods returning them should be deleted.
* The array contains a list of null terminated section starting with the name of the class
* to rename in which the methods are deleted, followed by a list of return types identifying
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
index c988c70..2016c0e 100755
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
@@ -527,7 +527,8 @@
// field instruction
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- // name is the field's name.
+ // owner is the class that declares the field.
+ considerName(owner);
// desc is the field's descriptor (see Type).
considerDesc(desc);
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
index 40c1706..9387814 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -62,4 +62,11 @@
*/
public abstract String[] getDeleteReturns();
+ /**
+ * Returns the list of classes to refactor, must be an even list: the
+ * binary FQCN of class to replace followed by the new FQCN. All references
+ * to the old class should be updated to the new class.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getJavaPkgClasses();
}
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 9cd74db..bc4caf2 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
@@ -18,6 +18,7 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -86,7 +87,9 @@
}
try {
- AsmGenerator agen = new AsmGenerator(log, osDestJar, new CreateInfo());
+ CreateInfo info = new CreateInfo();
+ Set<String> excludeClasses = getExcludedClasses(info);
+ AsmGenerator agen = new AsmGenerator(log, osDestJar, info);
AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
new String[] { // derived from
@@ -110,7 +113,13 @@
"android.pim.*", // for datepicker
"android.os.*", // for android.os.Handler
"android.database.ContentObserver", // for Digital clock
- });
+ "com.android.i18n.phonenumbers.*", // for TextView with autolink attribute
+ "com.android.internal.view.menu.ActionMenu",
+ },
+ excludeClasses,
+ new String[] {
+ "com/android/i18n/phonenumbers/data/*",
+ });
aa.analyze();
agen.generate();
@@ -146,6 +155,16 @@
return 1;
}
+ private static Set<String> getExcludedClasses(CreateInfo info) {
+ String[] refactoredClasses = info.getJavaPkgClasses();
+ Set<String> excludedClasses = new HashSet<String>(refactoredClasses.length);
+ for (int i = 0; i < refactoredClasses.length; i+=2) {
+ excludedClasses.add(refactoredClasses[i]);
+ }
+ return excludedClasses;
+
+ }
+
private static int listDeps(ArrayList<String> osJarPath, Log log) {
DependencyFinder df = new DependencyFinder(log);
try {
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java
new file mode 100644
index 0000000..91161f5
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.android.tools.layoutlib.create;
+
+import java.util.HashMap;
+
+import org.objectweb.asm.ClassVisitor;
+
+public class RefactorClassAdapter extends AbstractClassAdapter {
+
+ private final HashMap<String, String> mRefactorClasses;
+
+ RefactorClassAdapter(ClassVisitor cv, HashMap<String, String> refactorClasses) {
+ super(cv);
+ mRefactorClasses = refactorClasses;
+ }
+
+ @Override
+ protected String renameInternalType(String oldClassName) {
+ if (oldClassName != null) {
+ String newName = mRefactorClasses.get(oldClassName);
+ if (newName != null) {
+ return newName;
+ }
+ int pos = oldClassName.indexOf('$');
+ if (pos > 0) {
+ newName = mRefactorClasses.get(oldClassName.substring(0, pos));
+ if (newName != null) {
+ return newName + oldClassName.substring(pos);
+ }
+ }
+ }
+ return oldClassName;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
index 383cbb8..661074c 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
@@ -16,17 +16,7 @@
package com.android.tools.layoutlib.create;
-import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.signature.SignatureReader;
-import org.objectweb.asm.signature.SignatureVisitor;
-import org.objectweb.asm.signature.SignatureWriter;
/**
* This class visitor renames a class from a given old name to a given new name.
@@ -36,7 +26,7 @@
* For inner classes, this handles only the case where the outer class name changes.
* The inner class name should remain the same.
*/
-public class RenameClassAdapter extends ClassVisitor {
+public class RenameClassAdapter extends AbstractClassAdapter {
private final String mOldName;
@@ -49,8 +39,8 @@
* The class visitor will also rename all inner classes and references in the methods.
* The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
*/
- public RenameClassAdapter(ClassWriter cv, String oldName, String newName) {
- super(Opcodes.ASM4, cv);
+ public RenameClassAdapter(ClassVisitor cv, String oldName, String newName) {
+ super(cv);
mOldBase = mOldName = oldName;
mNewBase = mNewName = newName;
@@ -66,71 +56,6 @@
assert (mOldBase == null && mNewBase == null) || (mOldBase != null && mNewBase != null);
}
-
- /**
- * Renames a type descriptor, e.g. "Lcom.package.MyClass;"
- * If the type doesn't need to be renamed, returns the input string as-is.
- */
- String renameTypeDesc(String desc) {
- if (desc == null) {
- return null;
- }
-
- return renameType(Type.getType(desc));
- }
-
- /**
- * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
- * object element, e.g. "[Lcom.package.MyClass;"
- * If the type doesn't need to be renamed, returns the internal name of the input type.
- */
- String renameType(Type type) {
- if (type == null) {
- return null;
- }
-
- if (type.getSort() == Type.OBJECT) {
- String in = type.getInternalName();
- return "L" + renameInternalType(in) + ";";
- } else if (type.getSort() == Type.ARRAY) {
- StringBuilder sb = new StringBuilder();
- for (int n = type.getDimensions(); n > 0; n--) {
- sb.append('[');
- }
- sb.append(renameType(type.getElementType()));
- return sb.toString();
- }
- return type.getDescriptor();
- }
-
- /**
- * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
- * object element, e.g. "[Lcom.package.MyClass;".
- * This is like renameType() except that it returns a Type object.
- * If the type doesn't need to be renamed, returns the input type object.
- */
- Type renameTypeAsType(Type type) {
- if (type == null) {
- return null;
- }
-
- if (type.getSort() == Type.OBJECT) {
- String in = type.getInternalName();
- String newIn = renameInternalType(in);
- if (newIn != in) {
- return Type.getType("L" + newIn + ";");
- }
- } else if (type.getSort() == Type.ARRAY) {
- StringBuilder sb = new StringBuilder();
- for (int n = type.getDimensions(); n > 0; n--) {
- sb.append('[');
- }
- sb.append(renameType(type.getElementType()));
- return Type.getType(sb.toString());
- }
- return type;
- }
-
/**
* Renames an internal type name, e.g. "com.package.MyClass".
* If the type doesn't need to be renamed, returns the input string as-is.
@@ -138,7 +63,8 @@
* The internal type of some of the MethodVisitor turns out to be a type
descriptor sometimes so descriptors are renamed too.
*/
- String renameInternalType(String type) {
+ @Override
+ protected String renameInternalType(String type) {
if (type == null) {
return null;
}
@@ -155,309 +81,7 @@
if (pos == mOldBase.length() && type.startsWith(mOldBase)) {
return mNewBase + type.substring(pos);
}
-
- // The internal type of some of the MethodVisitor turns out to be a type
- // descriptor sometimes. This is the case with visitTypeInsn(type) and
- // visitMethodInsn(owner). We try to detect it and adjust it here.
- if (type.indexOf(';') > 0) {
- type = renameTypeDesc(type);
- }
-
return type;
}
- /**
- * Renames a method descriptor, i.e. applies renameType to all arguments and to the
- * return value.
- */
- String renameMethodDesc(String desc) {
- if (desc == null) {
- return null;
- }
-
- Type[] args = Type.getArgumentTypes(desc);
-
- StringBuilder sb = new StringBuilder("(");
- for (Type arg : args) {
- String name = renameType(arg);
- sb.append(name);
- }
- sb.append(')');
-
- Type ret = Type.getReturnType(desc);
- String name = renameType(ret);
- sb.append(name);
-
- return sb.toString();
- }
-
-
- /**
- * Renames the ClassSignature handled by ClassVisitor.visit
- * or the MethodTypeSignature handled by ClassVisitor.visitMethod.
- */
- String renameTypeSignature(String sig) {
- if (sig == null) {
- return null;
- }
- SignatureReader reader = new SignatureReader(sig);
- SignatureWriter writer = new SignatureWriter();
- reader.accept(new RenameSignatureAdapter(writer));
- sig = writer.toString();
- return sig;
- }
-
-
- /**
- * Renames the FieldTypeSignature handled by ClassVisitor.visitField
- * or MethodVisitor.visitLocalVariable.
- */
- String renameFieldSignature(String sig) {
- if (sig == null) {
- return null;
- }
- SignatureReader reader = new SignatureReader(sig);
- SignatureWriter writer = new SignatureWriter();
- reader.acceptType(new RenameSignatureAdapter(writer));
- sig = writer.toString();
- return sig;
- }
-
-
- //----------------------------------
- // Methods from the ClassAdapter
-
- @Override
- public void visit(int version, int access, String name, String signature,
- String superName, String[] interfaces) {
- name = renameInternalType(name);
- superName = renameInternalType(superName);
- signature = renameTypeSignature(signature);
-
- super.visit(version, access, name, signature, superName, interfaces);
- }
-
- @Override
- public void visitInnerClass(String name, String outerName, String innerName, int access) {
- assert outerName.equals(mOldName);
- outerName = renameInternalType(outerName);
- name = outerName + "$" + innerName;
- super.visitInnerClass(name, outerName, innerName, access);
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
- desc = renameMethodDesc(desc);
- signature = renameTypeSignature(signature);
- MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
- return new RenameMethodAdapter(mw);
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- desc = renameTypeDesc(desc);
- return super.visitAnnotation(desc, visible);
- }
-
- @Override
- public FieldVisitor visitField(int access, String name, String desc,
- String signature, Object value) {
- desc = renameTypeDesc(desc);
- signature = renameFieldSignature(signature);
- return super.visitField(access, name, desc, signature, value);
- }
-
-
- //----------------------------------
-
- /**
- * A method visitor that renames all references from an old class name to a new class name.
- */
- public class RenameMethodAdapter extends MethodVisitor {
-
- /**
- * Creates a method visitor that renames all references from a given old name to a given new
- * name. The method visitor will also rename all inner classes.
- * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
- */
- public RenameMethodAdapter(MethodVisitor mv) {
- super(Opcodes.ASM4, mv);
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- desc = renameTypeDesc(desc);
-
- return super.visitAnnotation(desc, visible);
- }
-
- @Override
- public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
- desc = renameTypeDesc(desc);
-
- return super.visitParameterAnnotation(parameter, desc, visible);
- }
-
- @Override
- public void visitTypeInsn(int opcode, String type) {
- type = renameInternalType(type);
-
- super.visitTypeInsn(opcode, type);
- }
-
- @Override
- public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- owner = renameInternalType(owner);
- desc = renameTypeDesc(desc);
-
- super.visitFieldInsn(opcode, owner, name, desc);
- }
-
- @Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
- owner = renameInternalType(owner);
- desc = renameMethodDesc(desc);
-
- super.visitMethodInsn(opcode, owner, name, desc);
- }
-
- @Override
- public void visitLdcInsn(Object cst) {
- // If cst is a Type, this means the code is trying to pull the .class constant
- // for this class, so it needs to be renamed too.
- if (cst instanceof Type) {
- cst = renameTypeAsType((Type) cst);
- }
- super.visitLdcInsn(cst);
- }
-
- @Override
- public void visitMultiANewArrayInsn(String desc, int dims) {
- desc = renameTypeDesc(desc);
-
- super.visitMultiANewArrayInsn(desc, dims);
- }
-
- @Override
- public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
- type = renameInternalType(type);
-
- super.visitTryCatchBlock(start, end, handler, type);
- }
-
- @Override
- public void visitLocalVariable(String name, String desc, String signature,
- Label start, Label end, int index) {
- desc = renameTypeDesc(desc);
- signature = renameFieldSignature(signature);
-
- super.visitLocalVariable(name, desc, signature, start, end, index);
- }
-
- }
-
- //----------------------------------
-
- public class RenameSignatureAdapter extends SignatureVisitor {
-
- private final SignatureVisitor mSv;
-
- public RenameSignatureAdapter(SignatureVisitor sv) {
- super(Opcodes.ASM4);
- mSv = sv;
- }
-
- @Override
- public void visitClassType(String name) {
- name = renameInternalType(name);
- mSv.visitClassType(name);
- }
-
- @Override
- public void visitInnerClassType(String name) {
- name = renameInternalType(name);
- mSv.visitInnerClassType(name);
- }
-
- @Override
- public SignatureVisitor visitArrayType() {
- SignatureVisitor sv = mSv.visitArrayType();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public void visitBaseType(char descriptor) {
- mSv.visitBaseType(descriptor);
- }
-
- @Override
- public SignatureVisitor visitClassBound() {
- SignatureVisitor sv = mSv.visitClassBound();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public void visitEnd() {
- mSv.visitEnd();
- }
-
- @Override
- public SignatureVisitor visitExceptionType() {
- SignatureVisitor sv = mSv.visitExceptionType();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public void visitFormalTypeParameter(String name) {
- mSv.visitFormalTypeParameter(name);
- }
-
- @Override
- public SignatureVisitor visitInterface() {
- SignatureVisitor sv = mSv.visitInterface();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public SignatureVisitor visitInterfaceBound() {
- SignatureVisitor sv = mSv.visitInterfaceBound();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public SignatureVisitor visitParameterType() {
- SignatureVisitor sv = mSv.visitParameterType();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public SignatureVisitor visitReturnType() {
- SignatureVisitor sv = mSv.visitReturnType();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public SignatureVisitor visitSuperclass() {
- SignatureVisitor sv = mSv.visitSuperclass();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public void visitTypeArgument() {
- mSv.visitTypeArgument();
- }
-
- @Override
- public SignatureVisitor visitTypeArgument(char wildcard) {
- SignatureVisitor sv = mSv.visitTypeArgument(wildcard);
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public void visitTypeVariable(String name) {
- mSv.visitTypeVariable(name);
- }
-
- }
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
new file mode 100644
index 0000000..ed2c128
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.android.tools.layoutlib.java;
+
+/**
+ * Defines the same interface as the java.lang.AutoCloseable which was added in
+ * Java 7. This hack makes it possible to run the Android code which uses Java 7
+ * features (API 18 and beyond) to run on Java 6.
+ * <p/>
+ * Extracted from API level 18, file:
+ * platform/libcore/luni/src/main/java/java/lang/AutoCloseable.java
+ */
+public interface AutoCloseable {
+ /**
+ * Closes the object and release any system resources it holds.
+ */
+ void close() throws Exception; }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Charsets.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Charsets.java
new file mode 100644
index 0000000..f73b06b
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Charsets.java
@@ -0,0 +1,133 @@
+/*
+ * 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 com.android.tools.layoutlib.java;
+
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+/**
+ * Defines the same class as the java.nio.charset.Charsets which was added in
+ * Dalvik VM. This hack, provides a replacement for that class which can't be
+ * loaded in the standard JVM since it's in the java package and standard JVM
+ * doesn't have it. An implementation of the native methods in the original
+ * class has been added.
+ * <p/>
+ * Extracted from API level 18, file:
+ * platform/libcore/luni/src/main/java/java/nio/charset/Charsets
+ */
+public final class Charsets {
+ /**
+ * A cheap and type-safe constant for the ISO-8859-1 Charset.
+ */
+ public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
+
+ /**
+ * A cheap and type-safe constant for the US-ASCII Charset.
+ */
+ public static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+ /**
+ * A cheap and type-safe constant for the UTF-8 Charset.
+ */
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ /**
+ * Returns a new byte array containing the bytes corresponding to the given characters,
+ * encoded in US-ASCII. Unrepresentable characters are replaced by (byte) '?'.
+ */
+ public static byte[] toAsciiBytes(char[] chars, int offset, int length) {
+ CharBuffer cb = CharBuffer.allocate(length);
+ cb.put(chars, offset, length);
+ return US_ASCII.encode(cb).array();
+ }
+
+ /**
+ * Returns a new byte array containing the bytes corresponding to the given characters,
+ * encoded in ISO-8859-1. Unrepresentable characters are replaced by (byte) '?'.
+ */
+ public static byte[] toIsoLatin1Bytes(char[] chars, int offset, int length) {
+ CharBuffer cb = CharBuffer.allocate(length);
+ cb.put(chars, offset, length);
+ return ISO_8859_1.encode(cb).array();
+ }
+
+ /**
+ * Returns a new byte array containing the bytes corresponding to the given characters,
+ * encoded in UTF-8. All characters are representable in UTF-8.
+ */
+ public static byte[] toUtf8Bytes(char[] chars, int offset, int length) {
+ CharBuffer cb = CharBuffer.allocate(length);
+ cb.put(chars, offset, length);
+ return UTF_8.encode(cb).array();
+ }
+
+ /**
+ * Returns a new byte array containing the bytes corresponding to the given characters,
+ * encoded in UTF-16BE. All characters are representable in UTF-16BE.
+ */
+ public static byte[] toBigEndianUtf16Bytes(char[] chars, int offset, int length) {
+ byte[] result = new byte[length * 2];
+ int end = offset + length;
+ int resultIndex = 0;
+ for (int i = offset; i < end; ++i) {
+ char ch = chars[i];
+ result[resultIndex++] = (byte) (ch >> 8);
+ result[resultIndex++] = (byte) ch;
+ }
+ return result;
+ }
+
+ /**
+ * Decodes the given US-ASCII bytes into the given char[]. Equivalent to but faster than:
+ *
+ * for (int i = 0; i < count; ++i) {
+ * char ch = (char) (data[start++] & 0xff);
+ * value[i] = (ch <= 0x7f) ? ch : REPLACEMENT_CHAR;
+ * }
+ */
+ public static void asciiBytesToChars(byte[] bytes, int offset, int length, char[] chars) {
+ if (bytes == null || chars == null) {
+ return;
+ }
+ final char REPLACEMENT_CHAR = (char)0xffd;
+ int start = offset;
+ for (int i = 0; i < length; ++i) {
+ char ch = (char) (bytes[start++] & 0xff);
+ chars[i] = (ch <= 0x7f) ? ch : REPLACEMENT_CHAR;
+ }
+ }
+
+ /**
+ * Decodes the given ISO-8859-1 bytes into the given char[]. Equivalent to but faster than:
+ *
+ * for (int i = 0; i < count; ++i) {
+ * value[i] = (char) (data[start++] & 0xff);
+ * }
+ */
+ public static void isoLatin1BytesToChars(byte[] bytes, int offset, int length, char[] chars) {
+ if (bytes == null || chars == null) {
+ return;
+ }
+ int start = offset;
+ for (int i = 0; i < length; ++i) {
+ chars[i] = (char) (bytes[start++] & 0xff);
+ }
+ }
+
+ private Charsets() {
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/IntegralToString.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/IntegralToString.java
new file mode 100644
index 0000000..e6dd00a
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/IntegralToString.java
@@ -0,0 +1,537 @@
+/*
+ * 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 com.android.tools.layoutlib.java;
+
+/**
+ * Defines the same class as the java.lang.IntegralToString which was added in
+ * Dalvik VM. This hack, provides a replacement for that class which can't be
+ * loaded in the standard JVM since it's in the java package and standard JVM
+ * doesn't have it. Since it's no longer in java.lang, access to package
+ * private methods and classes has been replaced by the closes matching public
+ * implementation.
+ * <p/>
+ * Extracted from API level 18, file:
+ * platform/libcore/luni/src/main/java/java/lang/IntegralToString.java
+ */
+public final class IntegralToString {
+ /**
+ * When appending to an AbstractStringBuilder, this thread-local char[] lets us avoid
+ * allocation of a temporary array. (We can't write straight into the AbstractStringBuilder
+ * because it's almost as expensive to work out the exact length of the result as it is to
+ * do the formatting. We could try being conservative and "delete"-ing the unused space
+ * afterwards, but then we'd need to duplicate convertInt and convertLong rather than share
+ * the code.)
+ */
+ private static final ThreadLocal<char[]> BUFFER = new ThreadLocal<char[]>() {
+ @Override protected char[] initialValue() {
+ return new char[20]; // Maximum length of a base-10 long.
+ }
+ };
+
+ /**
+ * These tables are used to special-case toString computation for
+ * small values. This serves three purposes: it reduces memory usage;
+ * it increases performance for small values; and it decreases the
+ * number of comparisons required to do the length computation.
+ * Elements of this table are lazily initialized on first use.
+ * No locking is necessary, i.e., we use the non-volatile, racy
+ * single-check idiom.
+ */
+ private static final String[] SMALL_NONNEGATIVE_VALUES = new String[100];
+ private static final String[] SMALL_NEGATIVE_VALUES = new String[100];
+
+ /** TENS[i] contains the tens digit of the number i, 0 <= i <= 99. */
+ private static final char[] TENS = {
+ '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
+ '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
+ '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
+ '3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
+ '4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
+ '5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
+ '6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
+ '7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
+ '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
+ '9', '9', '9', '9', '9', '9', '9', '9', '9', '9'
+ };
+
+ /** Ones [i] contains the tens digit of the number i, 0 <= i <= 99. */
+ private static final char[] ONES = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ };
+
+ /**
+ * Table for MOD / DIV 10 computation described in Section 10-21
+ * of Hank Warren's "Hacker's Delight" online addendum.
+ * http://www.hackersdelight.org/divcMore.pdf
+ */
+ private static final char[] MOD_10_TABLE = {
+ 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9, 0
+ };
+
+ /**
+ * The digits for every supported radix.
+ */
+ private static final char[] DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z'
+ };
+
+ private static final char[] UPPER_CASE_DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z'
+ };
+
+ private IntegralToString() {
+ }
+
+ /**
+ * Equivalent to Integer.toString(i, radix).
+ */
+ public static String intToString(int i, int radix) {
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ radix = 10;
+ }
+ if (radix == 10) {
+ return intToString(i);
+ }
+
+ /*
+ * If i is positive, negate it. This is the opposite of what one might
+ * expect. It is necessary because the range of the negative values is
+ * strictly larger than that of the positive values: there is no
+ * positive value corresponding to Integer.MIN_VALUE.
+ */
+ boolean negative = false;
+ if (i < 0) {
+ negative = true;
+ } else {
+ i = -i;
+ }
+
+ int bufLen = radix < 8 ? 33 : 12; // Max chars in result (conservative)
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ int q = i / radix;
+ buf[--cursor] = DIGITS[radix * q - i];
+ i = q;
+ } while (i != 0);
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ /**
+ * Equivalent to Integer.toString(i).
+ */
+ public static String intToString(int i) {
+ return convertInt(null, i);
+ }
+
+ /**
+ * Equivalent to sb.append(Integer.toString(i)).
+ */
+ public static void appendInt(StringBuilder sb, int i) {
+ convertInt(sb, i);
+ }
+
+ /**
+ * Returns the string representation of i and leaves sb alone if sb is null.
+ * Returns null and appends the string representation of i to sb if sb is non-null.
+ */
+ private static String convertInt(StringBuilder sb, int i) {
+ boolean negative = false;
+ String quickResult = null;
+ if (i < 0) {
+ negative = true;
+ i = -i;
+ if (i < 100) {
+ if (i < 0) {
+ // If -n is still negative, n is Integer.MIN_VALUE
+ quickResult = "-2147483648";
+ } else {
+ quickResult = SMALL_NEGATIVE_VALUES[i];
+ if (quickResult == null) {
+ SMALL_NEGATIVE_VALUES[i] = quickResult =
+ i < 10 ? stringOf('-', ONES[i]) : stringOf('-', TENS[i], ONES[i]);
+ }
+ }
+ }
+ } else {
+ if (i < 100) {
+ quickResult = SMALL_NONNEGATIVE_VALUES[i];
+ if (quickResult == null) {
+ SMALL_NONNEGATIVE_VALUES[i] = quickResult =
+ i < 10 ? stringOf(ONES[i]) : stringOf(TENS[i], ONES[i]);
+ }
+ }
+ }
+ if (quickResult != null) {
+ if (sb != null) {
+ sb.append(quickResult);
+ return null;
+ }
+ return quickResult;
+ }
+
+ int bufLen = 11; // Max number of chars in result
+ char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
+ int cursor = bufLen;
+
+ // Calculate digits two-at-a-time till remaining digits fit in 16 bits
+ while (i >= (1 << 16)) {
+ // Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8
+ int q = (int) ((0x51EB851FL * i) >>> 37);
+ int r = i - 100*q;
+ buf[--cursor] = ONES[r];
+ buf[--cursor] = TENS[r];
+ i = q;
+ }
+
+ // Calculate remaining digits one-at-a-time for performance
+ while (i != 0) {
+ // Compute q = n/10 and r = n % 10 as per "Hacker's Delight" 10-8
+ int q = (0xCCCD * i) >>> 19;
+ int r = i - 10*q;
+ buf[--cursor] = DIGITS[r];
+ i = q;
+ }
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+
+ if (sb != null) {
+ sb.append(buf, cursor, bufLen - cursor);
+ return null;
+ } else {
+ return new String(buf, cursor, bufLen - cursor);
+ }
+ }
+
+ /**
+ * Equivalent to Long.toString(v, radix).
+ */
+ public static String longToString(long v, int radix) {
+ int i = (int) v;
+ if (i == v) {
+ return intToString(i, radix);
+ }
+
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ radix = 10;
+ }
+ if (radix == 10) {
+ return longToString(v);
+ }
+
+ /*
+ * If v is positive, negate it. This is the opposite of what one might
+ * expect. It is necessary because the range of the negative values is
+ * strictly larger than that of the positive values: there is no
+ * positive value corresponding to Integer.MIN_VALUE.
+ */
+ boolean negative = false;
+ if (v < 0) {
+ negative = true;
+ } else {
+ v = -v;
+ }
+
+ int bufLen = radix < 8 ? 65 : 23; // Max chars in result (conservative)
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ long q = v / radix;
+ buf[--cursor] = DIGITS[(int) (radix * q - v)];
+ v = q;
+ } while (v != 0);
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ /**
+ * Equivalent to Long.toString(l).
+ */
+ public static String longToString(long l) {
+ return convertLong(null, l);
+ }
+
+ /**
+ * Equivalent to sb.append(Long.toString(l)).
+ */
+ public static void appendLong(StringBuilder sb, long l) {
+ convertLong(sb, l);
+ }
+
+ /**
+ * Returns the string representation of n and leaves sb alone if sb is null.
+ * Returns null and appends the string representation of n to sb if sb is non-null.
+ */
+ private static String convertLong(StringBuilder sb, long n) {
+ int i = (int) n;
+ if (i == n) {
+ return convertInt(sb, i);
+ }
+
+ boolean negative = (n < 0);
+ if (negative) {
+ n = -n;
+ if (n < 0) {
+ // If -n is still negative, n is Long.MIN_VALUE
+ String quickResult = "-9223372036854775808";
+ if (sb != null) {
+ sb.append(quickResult);
+ return null;
+ }
+ return quickResult;
+ }
+ }
+
+ int bufLen = 20; // Maximum number of chars in result
+ char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
+
+ int low = (int) (n % 1000000000); // Extract low-order 9 digits
+ int cursor = intIntoCharArray(buf, bufLen, low);
+
+ // Zero-pad Low order part to 9 digits
+ while (cursor != (bufLen - 9)) {
+ buf[--cursor] = '0';
+ }
+
+ /*
+ * The remaining digits are (n - low) / 1,000,000,000. This
+ * "exact division" is done as per the online addendum to Hank Warren's
+ * "Hacker's Delight" 10-20, http://www.hackersdelight.org/divcMore.pdf
+ */
+ n = ((n - low) >>> 9) * 0x8E47CE423A2E9C6DL;
+
+ /*
+ * If the remaining digits fit in an int, emit them using a
+ * single call to intIntoCharArray. Otherwise, strip off the
+ * low-order digit, put it in buf, and then call intIntoCharArray
+ * on the remaining digits (which now fit in an int).
+ */
+ if ((n & (-1L << 32)) == 0) {
+ cursor = intIntoCharArray(buf, cursor, (int) n);
+ } else {
+ /*
+ * Set midDigit to n % 10
+ */
+ int lo32 = (int) n;
+ int hi32 = (int) (n >>> 32);
+
+ // midDigit = ((unsigned) low32) % 10, per "Hacker's Delight" 10-21
+ int midDigit = MOD_10_TABLE[(0x19999999 * lo32 + (lo32 >>> 1) + (lo32 >>> 3)) >>> 28];
+
+ // Adjust midDigit for hi32. (assert hi32 == 1 || hi32 == 2)
+ midDigit -= hi32 << 2; // 1L << 32 == -4 MOD 10
+ if (midDigit < 0) {
+ midDigit += 10;
+ }
+ buf[--cursor] = DIGITS[midDigit];
+
+ // Exact division as per Warren 10-20
+ int rest = ((int) ((n - midDigit) >>> 1)) * 0xCCCCCCCD;
+ cursor = intIntoCharArray(buf, cursor, rest);
+ }
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+ if (sb != null) {
+ sb.append(buf, cursor, bufLen - cursor);
+ return null;
+ } else {
+ return new String(buf, cursor, bufLen - cursor);
+ }
+ }
+
+ /**
+ * Inserts the unsigned decimal integer represented by n into the specified
+ * character array starting at position cursor. Returns the index after
+ * the last character inserted (i.e., the value to pass in as cursor the
+ * next time this method is called). Note that n is interpreted as a large
+ * positive integer (not a negative integer) if its sign bit is set.
+ */
+ private static int intIntoCharArray(char[] buf, int cursor, int n) {
+ // Calculate digits two-at-a-time till remaining digits fit in 16 bits
+ while ((n & 0xffff0000) != 0) {
+ /*
+ * Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8.
+ * This computation is slightly different from the corresponding
+ * computation in intToString: the shifts before and after
+ * multiply can't be combined, as that would yield the wrong result
+ * if n's sign bit were set.
+ */
+ int q = (int) ((0x51EB851FL * (n >>> 2)) >>> 35);
+ int r = n - 100*q;
+ buf[--cursor] = ONES[r];
+ buf[--cursor] = TENS[r];
+ n = q;
+ }
+
+ // Calculate remaining digits one-at-a-time for performance
+ while (n != 0) {
+ // Compute q = n / 10 and r = n % 10 as per "Hacker's Delight" 10-8
+ int q = (0xCCCD * n) >>> 19;
+ int r = n - 10*q;
+ buf[--cursor] = DIGITS[r];
+ n = q;
+ }
+ return cursor;
+ }
+
+ public static String intToBinaryString(int i) {
+ int bufLen = 32; // Max number of binary digits in an int
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[i & 1];
+ } while ((i >>>= 1) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String longToBinaryString(long v) {
+ int i = (int) v;
+ if (v >= 0 && i == v) {
+ return intToBinaryString(i);
+ }
+
+ int bufLen = 64; // Max number of binary digits in a long
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[((int) v) & 1];
+ } while ((v >>>= 1) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ sb.append(digits[(b >> 4) & 0xf]);
+ sb.append(digits[b & 0xf]);
+ return sb;
+ }
+
+ public static String byteToHexString(byte b, boolean upperCase) {
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ char[] buf = new char[2]; // We always want two digits.
+ buf[0] = digits[(b >> 4) & 0xf];
+ buf[1] = digits[b & 0xf];
+ return new String(buf, 0, 2);
+ }
+
+ public static String bytesToHexString(byte[] bytes, boolean upperCase) {
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ char[] buf = new char[bytes.length * 2];
+ int c = 0;
+ for (byte b : bytes) {
+ buf[c++] = digits[(b >> 4) & 0xf];
+ buf[c++] = digits[b & 0xf];
+ }
+ return new String(buf);
+ }
+
+ public static String intToHexString(int i, boolean upperCase, int minWidth) {
+ int bufLen = 8; // Max number of hex digits in an int
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ do {
+ buf[--cursor] = digits[i & 0xf];
+ } while ((i >>>= 4) != 0 || (bufLen - cursor < minWidth));
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String longToHexString(long v) {
+ int i = (int) v;
+ if (v >= 0 && i == v) {
+ return intToHexString(i, false, 0);
+ }
+
+ int bufLen = 16; // Max number of hex digits in a long
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[((int) v) & 0xF];
+ } while ((v >>>= 4) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String intToOctalString(int i) {
+ int bufLen = 11; // Max number of octal digits in an int
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[i & 7];
+ } while ((i >>>= 3) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String longToOctalString(long v) {
+ int i = (int) v;
+ if (v >= 0 && i == v) {
+ return intToOctalString(i);
+ }
+ int bufLen = 22; // Max number of octal digits in a long
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[((int) v) & 7];
+ } while ((v >>>= 3) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ private static String stringOf(char... args) {
+ return new String(args, 0, args.length);
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/ModifiedUtf8.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/ModifiedUtf8.java
new file mode 100644
index 0000000..77c4f3a
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/ModifiedUtf8.java
@@ -0,0 +1,147 @@
+/*
+ * 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 com.android.tools.layoutlib.java;
+
+import java.io.UTFDataFormatException;
+import java.nio.ByteOrder;
+
+/**
+ * Defines the same class as the java.nio.charset.ModifiedUtf8 which was added in
+ * Dalvik VM. This hack, provides a replacement for that class which can't be
+ * loaded in the standard JVM since it's in the java package and standard JVM
+ * doesn't have it. An implementation of the native methods in the original
+ * class has been added.
+ * <p/>
+ * Extracted from API level 17, file:
+ * platform/libcore/luni/src/main/java/java/nio/charset/ModifiedUtf8
+ */
+public class ModifiedUtf8 {
+ /**
+ * Decodes a byte array containing <i>modified UTF-8</i> bytes into a string.
+ *
+ * <p>Note that although this method decodes the (supposedly impossible) zero byte to U+0000,
+ * that's what the RI does too.
+ */
+ public static String decode(byte[] in, char[] out, int offset, int utfSize) throws UTFDataFormatException {
+ int count = 0, s = 0, a;
+ while (count < utfSize) {
+ if ((out[s] = (char) in[offset + count++]) < '\u0080') {
+ s++;
+ } else if (((a = out[s]) & 0xe0) == 0xc0) {
+ if (count >= utfSize) {
+ throw new UTFDataFormatException("bad second byte at " + count);
+ }
+ int b = in[offset + count++];
+ if ((b & 0xC0) != 0x80) {
+ throw new UTFDataFormatException("bad second byte at " + (count - 1));
+ }
+ out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
+ } else if ((a & 0xf0) == 0xe0) {
+ if (count + 1 >= utfSize) {
+ throw new UTFDataFormatException("bad third byte at " + (count + 1));
+ }
+ int b = in[offset + count++];
+ int c = in[offset + count++];
+ if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
+ throw new UTFDataFormatException("bad second or third byte at " + (count - 2));
+ }
+ out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
+ } else {
+ throw new UTFDataFormatException("bad byte at " + (count - 1));
+ }
+ }
+ return new String(out, 0, s);
+ }
+
+ /**
+ * Returns the number of bytes the modified UTF-8 representation of 's' would take. Note
+ * that this is just the space for the bytes representing the characters, not the length
+ * which precedes those bytes, because different callers represent the length differently,
+ * as two, four, or even eight bytes. If {@code shortLength} is true, we'll throw an
+ * exception if the string is too long for its length to be represented by a short.
+ */
+ public static long countBytes(String s, boolean shortLength) throws UTFDataFormatException {
+ long result = 0;
+ final int length = s.length();
+ for (int i = 0; i < length; ++i) {
+ char ch = s.charAt(i);
+ if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
+ ++result;
+ } else if (ch <= 2047) {
+ result += 2;
+ } else {
+ result += 3;
+ }
+ if (shortLength && result > 65535) {
+ throw new UTFDataFormatException("String more than 65535 UTF bytes long");
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Encodes the <i>modified UTF-8</i> bytes corresponding to string {@code s} into the
+ * byte array {@code dst}, starting at the given {@code offset}.
+ */
+ public static void encode(byte[] dst, int offset, String s) {
+ final int length = s.length();
+ for (int i = 0; i < length; i++) {
+ char ch = s.charAt(i);
+ if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
+ dst[offset++] = (byte) ch;
+ } else if (ch <= 2047) {
+ dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
+ dst[offset++] = (byte) (0x80 | (0x3f & ch));
+ } else {
+ dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12)));
+ dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6)));
+ dst[offset++] = (byte) (0x80 | (0x3f & ch));
+ }
+ }
+ }
+
+ /**
+ * Returns an array containing the <i>modified UTF-8</i> form of {@code s}, using a
+ * big-endian 16-bit length. Throws UTFDataFormatException if {@code s} is too long
+ * for a two-byte length.
+ */
+ public static byte[] encode(String s) throws UTFDataFormatException {
+ int utfCount = (int) ModifiedUtf8.countBytes(s, true);
+ // Original statement used libcore.io.SizeOf.SHORT. Since we don't have that, we
+ // substitute it's value directly, which is 2.
+ byte[] result = new byte[2 + utfCount];
+ // The original statement had libcore.io.Memory.pokeShort(). We provide it's implementation
+ // below in a private method.
+ pokeShort(result, 0, (short) utfCount, ByteOrder.BIG_ENDIAN);
+ ModifiedUtf8.encode(result, 2, s);
+ return result;
+ }
+
+ // Copied from libcore.io.Memory.pokeShort().
+ private static void pokeShort(byte[] dst, int offset, short value, ByteOrder order) {
+ if (order == ByteOrder.BIG_ENDIAN) {
+ dst[offset++] = (byte) ((value >> 8) & 0xff);
+ dst[offset ] = (byte) ((value >> 0) & 0xff);
+ } else {
+ dst[offset++] = (byte) ((value >> 0) & 0xff);
+ dst[offset ] = (byte) ((value >> 8) & 0xff);
+ }
+ }
+
+ private ModifiedUtf8() {
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java
new file mode 100644
index 0000000..0e09080
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java
@@ -0,0 +1,81 @@
+/*
+ * 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 com.android.tools.layoutlib.java;
+
+import java.nio.charset.Charset;
+
+/**
+ * Defines the same class as the java.lang.UnsafeByteSequence which was added in
+ * Dalvik VM. This hack, provides a replacement for that class which can't be
+ * loaded in the standard JVM since it's in the java package and standard JVM
+ * doesn't have it.
+ * <p/>
+ * Extracted from API level 18, file:
+ * platform/libcore/luni/src/main/java/java/lang/UnsafeByteSequence.java
+ */
+public class UnsafeByteSequence {
+ private byte[] bytes;
+ private int count;
+
+ public UnsafeByteSequence(int initialCapacity) {
+ this.bytes = new byte[initialCapacity];
+ }
+
+ public int size() {
+ return count;
+ }
+
+ /**
+ * Moves the write pointer back to the beginning of the sequence,
+ * but without resizing or reallocating the buffer.
+ */
+ public void rewind() {
+ count = 0;
+ }
+
+ public void write(byte[] buffer, int offset, int length) {
+ if (count + length >= bytes.length) {
+ byte[] newBytes = new byte[(count + length) * 2];
+ System.arraycopy(bytes, 0, newBytes, 0, count);
+ bytes = newBytes;
+ }
+ System.arraycopy(buffer, offset, bytes, count, length);
+ count += length;
+ }
+
+ public void write(int b) {
+ if (count == bytes.length) {
+ byte[] newBytes = new byte[count * 2];
+ System.arraycopy(bytes, 0, newBytes, 0, count);
+ bytes = newBytes;
+ }
+ bytes[count++] = (byte) b;
+ }
+
+ public byte[] toByteArray() {
+ if (count == bytes.length) {
+ return bytes;
+ }
+ byte[] result = new byte[count];
+ System.arraycopy(bytes, 0, result, 0, count);
+ return result;
+ }
+
+ public String toString(Charset cs) {
+ return new String(bytes, 0, count, cs);
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index d6dba6a..78e2c48 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -29,9 +29,12 @@
import org.objectweb.asm.ClassReader;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
/**
@@ -51,8 +54,12 @@
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
- mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */,
- null /* deriveFrom */, null /* includeGlobs */ );
+ Set<String> excludeClasses = new HashSet<String>(1);
+ excludeClasses.add("java.lang.JavaClass");
+
+ String[] includeFiles = new String[]{"mock_android/data/data*"};
+ mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, null /* deriveFrom */,
+ null /* includeGlobs */, excludeClasses, includeFiles);
}
@After
@@ -61,9 +68,14 @@
@Test
public void testParseZip() throws IOException {
- Map<String, ClassReader> map = mAa.parseZip(mOsJarPath);
+
+ Map<String, ClassReader> map = new TreeMap<String, ClassReader>();
+ Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+ mAa.parseZip(mOsJarPath, map, filesFound);
assertArrayEquals(new String[] {
+ "java.lang.JavaClass",
"mock_android.dummy.InnerTest",
"mock_android.dummy.InnerTest$DerivingClass",
"mock_android.dummy.InnerTest$MyGenerics1",
@@ -71,6 +83,7 @@
"mock_android.dummy.InnerTest$MyStaticInnerClass",
"mock_android.dummy.InnerTest$NotStaticInner1",
"mock_android.dummy.InnerTest$NotStaticInner2",
+ "mock_android.util.EmptyArray",
"mock_android.view.View",
"mock_android.view.ViewGroup",
"mock_android.view.ViewGroup$LayoutParams",
@@ -81,11 +94,17 @@
"mock_android.widget.TableLayout$LayoutParams"
},
map.keySet().toArray());
+ assertArrayEquals(new String[] {"mock_android/data/dataFile"},
+ filesFound.keySet().toArray());
}
@Test
public void testFindClass() throws IOException, LogAbortException {
- Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+
+ Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
+ Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+ mAa.parseZip(mOsJarPath, zipClasses, filesFound);
TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
@@ -100,7 +119,11 @@
@Test
public void testFindGlobs() throws IOException, LogAbortException {
- Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+
+ Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
+ Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+ mAa.parseZip(mOsJarPath, zipClasses, filesFound);
TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
// this matches classes, a package match returns nothing
@@ -159,7 +182,11 @@
@Test
public void testFindClassesDerivingFrom() throws LogAbortException, IOException {
- Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+
+ Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
+ Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+ mAa.parseZip(mOsJarPath, zipClasses, filesFound);
TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
@@ -181,21 +208,26 @@
@Test
public void testDependencyVisitor() throws IOException, LogAbortException {
- Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+
+ Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
+ Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+ mAa.parseZip(mOsJarPath, zipClasses, filesFound);
TreeMap<String, ClassReader> keep = new TreeMap<String, ClassReader>();
TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>();
TreeMap<String, ClassReader> out_deps = new TreeMap<String, ClassReader>();
- ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep);
+ ClassReader cr = mAa.findClass("mock_android.widget.LinearLayout", zipClasses, keep);
DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
// get first level dependencies
cr.accept(visitor, 0 /* flags */);
assertArrayEquals(new String[] {
+ "mock_android.util.EmptyArray",
"mock_android.view.ViewGroup",
- "mock_android.widget.TableLayout$LayoutParams",
+ "mock_android.widget.LinearLayout$LayoutParams",
},
out_deps.keySet().toArray());
@@ -221,7 +253,11 @@
for (ClassReader cr2 : in_deps.values()) {
cr2.accept(visitor, 0 /* flags */);
}
+ keep.putAll(new_keep);
assertArrayEquals(new String[] { }, out_deps.keySet().toArray());
+ assertArrayEquals(new String[] {
+ "mock_android.widget.LinearLayout",
+ }, keep.keySet().toArray());
}
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 7b76a5b..0dbc238 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -19,16 +19,30 @@
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
/**
* Unit tests for some methods of {@link AsmGenerator}.
@@ -40,6 +54,9 @@
private String mOsDestJar;
private File mTempFile;
+ // ASM internal name for the the class in java package that should be refactored.
+ private static final String JAVA_CLASS_NAME = "java/lang/JavaClass";
+
@Before
public void setUp() throws Exception {
mLog = new MockLog();
@@ -48,7 +65,7 @@
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
- mTempFile = File.createTempFile("mock", "jar");
+ mTempFile = File.createTempFile("mock", ".jar");
mOsDestJar = mTempFile.getAbsolutePath();
mTempFile.deleteOnExit();
}
@@ -97,6 +114,11 @@
}
@Override
+ public String[] getJavaPkgClasses() {
+ return new String[0];
+ }
+
+ @Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
return new String[0];
@@ -109,11 +131,211 @@
null, // derived from
new String[] { // include classes
"**"
- });
+ },
+ new HashSet<String>(0) /* excluded classes */,
+ new String[]{} /* include files */);
aa.analyze();
agen.generate();
Set<String> notRenamed = agen.getClassesNotRenamed();
assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
+
+ }
+
+ @Test
+ public void testClassRefactoring() throws IOException, LogAbortException {
+ ICreateInfo ci = new ICreateInfo() {
+ @Override
+ public Class<?>[] getInjectedClasses() {
+ // classes to inject in the final JAR
+ return new Class<?>[] {
+ com.android.tools.layoutlib.create.dataclass.JavaClass.class
+ };
+ }
+
+ @Override
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ @Override
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[0];
+ }
+
+ @Override
+ public String[] getJavaPkgClasses() {
+ // classes to refactor (so that we can replace them)
+ return new String[] {
+ "java.lang.JavaClass", "com.android.tools.layoutlib.create.dataclass.JavaClass",
+ };
+ }
+
+ @Override
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
+
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ null, // derived from
+ new String[] { // include classes
+ "**"
+ },
+ new HashSet<String>(1),
+ new String[] { /* include files */
+ "mock_android/data/data*"
+ });
+ aa.analyze();
+ agen.generate();
+ Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
+ Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ parseZip(mOsDestJar, output, filesFound);
+ boolean injectedClassFound = false;
+ for (ClassReader cr: output.values()) {
+ TestClassVisitor cv = new TestClassVisitor();
+ cr.accept(cv, 0);
+ injectedClassFound |= cv.mInjectedClassFound;
+ }
+ assertTrue(injectedClassFound);
+ assertArrayEquals(new String[] {"mock_android/data/dataFile"},
+ filesFound.keySet().toArray());
+ }
+
+ private void parseZip(String jarPath,
+ Map<String, ClassReader> classes,
+ Map<String, InputStream> filesFound) throws IOException {
+
+ ZipFile zip = new ZipFile(jarPath);
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ ZipEntry entry;
+ while (entries.hasMoreElements()) {
+ entry = entries.nextElement();
+ if (entry.getName().endsWith(".class")) {
+ ClassReader cr = new ClassReader(zip.getInputStream(entry));
+ String className = classReaderToClassName(cr);
+ classes.put(className, cr);
+ } else {
+ filesFound.put(entry.getName(), zip.getInputStream(entry));
+ }
+ }
+
+ }
+
+ private String classReaderToClassName(ClassReader classReader) {
+ if (classReader == null) {
+ return null;
+ } else {
+ return classReader.getClassName().replace('/', '.');
+ }
+ }
+
+ private class TestClassVisitor extends ClassVisitor {
+
+ boolean mInjectedClassFound = false;
+
+ TestClassVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ assertTrue(!getBase(name).equals(JAVA_CLASS_NAME));
+ if (name.equals("com/android/tools/layoutlib/create/dataclass/JavaClass")) {
+ mInjectedClassFound = true;
+ }
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ assertTrue(testType(Type.getType(desc)));
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ @SuppressWarnings("hiding")
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ return new MethodVisitor(Opcodes.ASM4, mv) {
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name,
+ String desc) {
+ assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
+ assertTrue(testType(Type.getType(desc)));
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (cst instanceof Type) {
+ assertTrue(testType((Type)cst));
+ }
+ super.visitLdcInsn(cst);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ assertTrue(!getBase(type).equals(JAVA_CLASS_NAME));
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String desc) {
+ assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
+ assertTrue(testType(Type.getType(desc)));
+ super.visitMethodInsn(opcode, owner, name, desc);
+ }
+
+ };
+ }
+
+ private boolean testType(Type type) {
+ int sort = type.getSort();
+ if (sort == Type.OBJECT) {
+ assertTrue(!getBase(type.getInternalName()).equals(JAVA_CLASS_NAME));
+ } else if (sort == Type.ARRAY) {
+ assertTrue(!getBase(type.getElementType().getInternalName())
+ .equals(JAVA_CLASS_NAME));
+ } else if (sort == Type.METHOD) {
+ boolean r = true;
+ for (Type t : type.getArgumentTypes()) {
+ r &= testType(t);
+ }
+ return r & testType(type.getReturnType());
+ }
+ return true;
+ }
+
+ private String getBase(String className) {
+ if (className == null) {
+ return null;
+ }
+ int pos = className.indexOf('$');
+ if (pos > 0) {
+ return className.substring(0, pos);
+ }
+ return className;
+ }
}
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
index 90c6a9c..6211e73 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
@@ -24,7 +24,7 @@
import org.junit.Test;
/**
- *
+ *
*/
public class RenameClassAdapterTest {
@@ -36,10 +36,10 @@
mOuter = new RenameClassAdapter(null, // cv
"com.pack.Old",
"org.blah.New");
-
+
mInner = new RenameClassAdapter(null, // cv
"com.pack.Old$Inner",
- "org.blah.New$Inner");
+ "org.blah.New$Inner");
}
@After
@@ -72,7 +72,7 @@
// arrays
assertEquals("[Lorg.blah.New;", mOuter.renameTypeDesc("[Lcom.pack.Old;"));
assertEquals("[[Lorg.blah.New;", mOuter.renameTypeDesc("[[Lcom.pack.Old;"));
-
+
assertEquals("[Lorg.blah.New;", mInner.renameTypeDesc("[Lcom.pack.Old;"));
assertEquals("[[Lorg.blah.New;", mInner.renameTypeDesc("[[Lcom.pack.Old;"));
}
@@ -93,10 +93,6 @@
*/
@Test
public void testRenameInternalType() {
- // a descriptor is not left untouched
- assertEquals("Lorg.blah.New;", mOuter.renameInternalType("Lcom.pack.Old;"));
- assertEquals("Lorg.blah.New$Inner;", mOuter.renameInternalType("Lcom.pack.Old$Inner;"));
-
// an actual FQCN
assertEquals("org.blah.New", mOuter.renameInternalType("com.pack.Old"));
assertEquals("org.blah.New$Inner", mOuter.renameInternalType("com.pack.Old$Inner"));
@@ -115,6 +111,6 @@
mOuter.renameMethodDesc("(IDLcom.pack.Old;[Lcom.pack.Old$Inner;)Lcom.pack.Old$Other;"));
}
-
+
}
diff --git a/tools/layoutlib/create/tests/mock_android/view/View.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java
similarity index 75%
copy from tools/layoutlib/create/tests/mock_android/view/View.java
copy to tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java
index a80a98d..9b5a918 100644
--- a/tools/layoutlib/create/tests/mock_android/view/View.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package mock_android.view;
+package com.android.tools.layoutlib.create.dataclass;
-public class View {
+public final class JavaClass {
+ public static final String test = "test";
}
diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar
index a7ea74f..c6ca3c4 100644
--- a/tools/layoutlib/create/tests/data/mock_android.jar
+++ b/tools/layoutlib/create/tests/data/mock_android.jar
Binary files differ
diff --git a/tools/layoutlib/create/tests/data/mock_android.jardesc b/tools/layoutlib/create/tests/data/mock_android.jardesc
deleted file mode 100644
index 95f7591..0000000
--- a/tools/layoutlib/create/tests/data/mock_android.jardesc
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="WINDOWS-1252" standalone="no"?>
-<jardesc>
- <jar path="C:/ralf/google/src/raphael-lapdroid/device/tools/layoutlib/create/tests/data/mock_android.jar"/>
- <options buildIfNeeded="true" compress="true" descriptionLocation="/layoutlib_create/tests/data/mock_android.jardesc" exportErrors="true" exportWarnings="true" includeDirectoryEntries="false" overwrite="false" saveDescription="true" storeRefactorings="false" useSourceFolders="false"/>
- <storedRefactorings deprecationInfo="true" structuralOnly="false"/>
- <selectedProjects/>
- <manifest generateManifest="true" manifestLocation="" manifestVersion="1.0" reuseManifest="false" saveManifest="false" usesManifest="true">
- <sealing sealJar="false">
- <packagesToSeal/>
- <packagesToUnSeal/>
- </sealing>
- </manifest>
- <selectedElements exportClassFiles="true" exportJavaFiles="false" exportOutputFolder="false">
- <javaElement handleIdentifier="=layoutlib_create/tests<mock_android.widget"/>
- <javaElement handleIdentifier="=layoutlib_create/tests<mock_android.view"/>
- <javaElement handleIdentifier="=layoutlib_create/tests<mock_android.dummy"/>
- </selectedElements>
-</jardesc>
diff --git a/tools/layoutlib/create/tests/mock_android/view/View.java b/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java
similarity index 80%
copy from tools/layoutlib/create/tests/mock_android/view/View.java
copy to tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java
index a80a98d..59612e9 100644
--- a/tools/layoutlib/create/tests/mock_android/view/View.java
+++ b/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package mock_android.view;
+package java.lang;
-public class View {
+public class JavaClass {
+ public static String test = "test";
}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile b/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile
new file mode 100644
index 0000000..ab29fbe
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile
@@ -0,0 +1 @@
+A simple data file that should *not* be copied to the output jar.
\ No newline at end of file
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile b/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile
new file mode 100644
index 0000000..9b01893
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile
@@ -0,0 +1 @@
+A simple data file that should be copied to the output jar unchanged.
\ No newline at end of file
diff --git a/tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java b/tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java
similarity index 89%
rename from tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java
rename to tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java
index e355ead..d3a1d05 100644
--- a/tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java
@@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
public class InnerTest {
@@ -66,13 +67,13 @@
}
}
- public <X> void genericMethod1(X a, X[] a) {
+ public <X> void genericMethod1(X a, X[] b) {
}
public <X, Y> void genericMethod2(X a, List<Y> b) {
}
- public <X, Y> void genericMethod3(X a, List<Y extends InnerTest> b) {
+ public <X, Y extends InnerTest> void genericMethod3(X a, List<Y> b) {
}
public <T extends InnerTest> void genericMethod4(T[] a, Collection<T> b, Collection<?> c) {
@@ -85,6 +86,6 @@
mInnerInstance = m;
mTheIntEnum = null;
mGeneric1 = new MyGenerics1();
- genericMethod(new DerivingClass[0], new ArrayList<DerivingClass>(), new ArrayList<InnerTest>());
+ genericMethod4(new DerivingClass[0], new ArrayList<DerivingClass>(), new ArrayList<InnerTest>());
}
}
diff --git a/tools/layoutlib/create/tests/mock_android/view/View.java b/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java
similarity index 81%
copy from tools/layoutlib/create/tests/mock_android/view/View.java
copy to tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java
index a80a98d..aaeebf6 100644
--- a/tools/layoutlib/create/tests/mock_android/view/View.java
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package mock_android.view;
+package mock_android.util;
-public class View {
+import java.lang.JavaClass;
+public class EmptyArray {
+
+ public static final Object[] OBJECT = new Object[0];
}
diff --git a/tools/layoutlib/create/tests/mock_android/view/View.java b/tools/layoutlib/create/tests/mock_data/mock_android/view/View.java
similarity index 91%
rename from tools/layoutlib/create/tests/mock_android/view/View.java
rename to tools/layoutlib/create/tests/mock_data/mock_android/view/View.java
index a80a98d..84ec8a9 100644
--- a/tools/layoutlib/create/tests/mock_android/view/View.java
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/view/View.java
@@ -16,6 +16,10 @@
package mock_android.view;
+import java.lang.JavaClass;
+
public class View {
+ String x = JavaClass.test;
+
}
diff --git a/tools/layoutlib/create/tests/mock_android/view/ViewGroup.java b/tools/layoutlib/create/tests/mock_data/mock_android/view/ViewGroup.java
similarity index 100%
rename from tools/layoutlib/create/tests/mock_android/view/ViewGroup.java
rename to tools/layoutlib/create/tests/mock_data/mock_android/view/ViewGroup.java
diff --git a/tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java b/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java
similarity index 84%
rename from tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java
rename to tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java
index 3870a63..af56c4b 100644
--- a/tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java
@@ -16,11 +16,13 @@
package mock_android.widget;
+import mock_android.util.EmptyArray;
import mock_android.view.ViewGroup;
public class LinearLayout extends ViewGroup {
- public class LayoutParams extends mock_android.view.ViewGroup.LayoutParams {
+ Object[] mObjects = EmptyArray.OBJECT;
+ public class LayoutParams extends MarginLayoutParams {
}
diff --git a/tools/layoutlib/create/tests/mock_android/widget/TableLayout.java b/tools/layoutlib/create/tests/mock_data/mock_android/widget/TableLayout.java
similarity index 100%
rename from tools/layoutlib/create/tests/mock_android/widget/TableLayout.java
rename to tools/layoutlib/create/tests/mock_data/mock_android/widget/TableLayout.java