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