Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fb175664 authored by Diego Perez's avatar Diego Perez
Browse files

Add support for AppCompat widgets

This allows previews to use appcompat attributes like
app:backgroundTint.

Bug: http://b.android.com/211349
Change-Id: Iaa5f1612c58a6a27d3948bfe1bc0373098c57720
(cherry picked from commit 2ddddbc86314fa16c51d0e5f0d6e55e0a6676d26)
parent 4a1bd499
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -584,6 +584,7 @@ public final class BridgeTypedArray extends TypedArray {
        if (value == null) {
            return defValue;
        }
        value = value.trim();

        // if the value is just an integer, return it.
        try {
@@ -595,6 +596,11 @@ public final class BridgeTypedArray extends TypedArray {
            // pass
        }

        if (value.startsWith("#")) {
            // this looks like a color, do not try to parse it
            return defValue;
        }

        // Handle the @id/<name>, @+id/<name> and @android:id/<name>
        // We need to return the exact value that was compiled (from the various R classes),
        // as these values can be reused internally with calls to findViewById().
+63 −19
Original line number Diff line number Diff line
@@ -41,9 +41,24 @@ import android.content.res.TypedArray;
import android.util.AttributeSet;

import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import java.util.Set;

import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW;
import static com.android.SdkConstants.BUTTON;
import static com.android.SdkConstants.CHECKED_TEXT_VIEW;
import static com.android.SdkConstants.CHECK_BOX;
import static com.android.SdkConstants.EDIT_TEXT;
import static com.android.SdkConstants.IMAGE_BUTTON;
import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW;
import static com.android.SdkConstants.RADIO_BUTTON;
import static com.android.SdkConstants.SEEK_BAR;
import static com.android.SdkConstants.SPINNER;
import static com.android.SdkConstants.TEXT_VIEW;
import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;

/**
@@ -52,6 +67,13 @@ import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
public final class BridgeInflater extends LayoutInflater {

    private final LayoutlibCallback mLayoutlibCallback;
    /**
     * If true, the inflater will try to replace the framework widgets with the AppCompat versions.
     * Ideally, this should be based on the activity being an AppCompat activity but since that is
     * not trivial to check from layoutlib, we currently base the decision on the current theme
     * being an AppCompat theme.
     */
    private boolean mLoadAppCompatViews;
    private boolean mIsInMerge = false;
    private ResourceReference mResourceReference;
    private Map<View, String> mOpenDrawerLayouts;
@@ -59,6 +81,15 @@ public final class BridgeInflater extends LayoutInflater {
    // Keep in sync with the same value in LayoutInflater.
    private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme };

    private static final String APPCOMPAT_WIDGET_PREFIX = "android.support.v7.widget.AppCompat";
    /** List of platform widgets that have an AppCompat version */
    private static final Set<String> APPCOMPAT_VIEWS = Collections.unmodifiableSet(
            new HashSet<>(
                    Arrays.asList(TEXT_VIEW, "ImageSwitcher", BUTTON, EDIT_TEXT, SPINNER,
                            IMAGE_BUTTON, CHECK_BOX, RADIO_BUTTON, CHECKED_TEXT_VIEW,
                            AUTO_COMPLETE_TEXT_VIEW, MULTI_AUTO_COMPLETE_TEXT_VIEW, "RatingBar",
                            SEEK_BAR)));

    /**
     * List of class prefixes which are tried first by default.
     * <p/>
@@ -74,13 +105,15 @@ public final class BridgeInflater extends LayoutInflater {
        return sClassPrefixList;
    }

    protected BridgeInflater(LayoutInflater original, Context newContext) {
    private BridgeInflater(LayoutInflater original, Context newContext) {
        super(original, newContext);
        newContext = getBaseContext(newContext);
        if (newContext instanceof BridgeContext) {
            mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback();
            mLoadAppCompatViews = ((BridgeContext) newContext).isAppCompatTheme();
        } else {
            mLayoutlibCallback = null;
            mLoadAppCompatViews = false;
        }
    }

@@ -90,10 +123,11 @@ public final class BridgeInflater extends LayoutInflater {
     * @param context The Android application context.
     * @param layoutlibCallback the {@link LayoutlibCallback} object.
     */
    public BridgeInflater(Context context, LayoutlibCallback layoutlibCallback) {
    public BridgeInflater(BridgeContext context, LayoutlibCallback layoutlibCallback) {
        super(context);
        mLayoutlibCallback = layoutlibCallback;
        mConstructorArgs[0] = context;
        mLoadAppCompatViews = context.isAppCompatTheme();
    }

    @Override
@@ -101,6 +135,15 @@ public final class BridgeInflater extends LayoutInflater {
        View view = null;

        try {
            if (mLoadAppCompatViews && APPCOMPAT_VIEWS.contains(name)) {
                // We are using an AppCompat theme so try to load the appcompat views
                view = loadCustomView(APPCOMPAT_WIDGET_PREFIX + name, attrs);

                if (view == null) {
                    mLoadAppCompatViews = false; // Do not try anymore
                }
            } else {

                // First try to find a class using the default Android prefixes
                for (String prefix : sClassPrefixList) {
                    try {
@@ -122,6 +165,7 @@ public final class BridgeInflater extends LayoutInflater {
                } catch (ClassNotFoundException e) {
                    // Ignore. We'll try again using the custom view loader below.
                }
            }

            // Finally try again using the custom view loader
            if (view == null) {
+32 −0
Original line number Diff line number Diff line
@@ -110,6 +110,7 @@ import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_AP
 */
@SuppressWarnings("deprecation")  // For use of Pair.
public final class BridgeContext extends Context {
    private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";

    /** The map adds cookies to each view so that IDE can link xml tags to views. */
    private final HashMap<View, Object> mViewKeyMap = new HashMap<>();
@@ -153,6 +154,7 @@ public final class BridgeContext extends Context {
    private ClassLoader mClassLoader;
    private IBinder mBinder;
    private PackageManager mPackageManager;
    private Boolean mIsThemeAppCompat;

    /**
     * Some applications that target both pre API 17 and post API 17, set the newer attrs to
@@ -479,6 +481,36 @@ public final class BridgeContext extends Context {
        return Pair.of(null, Boolean.FALSE);
    }

    /**
     * Returns whether the current selected theme is based on AppCompat
     */
    public boolean isAppCompatTheme() {
        // If a cached value exists, return it.
        if (mIsThemeAppCompat != null) {
            return mIsThemeAppCompat;
        }
        // Ideally, we should check if the corresponding activity extends
        // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
        StyleResourceValue defaultTheme = mRenderResources.getDefaultTheme();
        // We can't simply check for parent using resources.themeIsParentOf() since the
        // inheritance structure isn't really what one would expect. The first common parent
        // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
        boolean isThemeAppCompat = false;
        for (int i = 0; i < 50; i++) {
            if (defaultTheme == null) {
                break;
            }
            // for loop ensures that we don't run into cyclic theme inheritance.
            if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
                isThemeAppCompat = true;
                break;
            }
            defaultTheme = mRenderResources.getParent(defaultTheme);
        }
        mIsThemeAppCompat = isThemeAppCompat;
        return isThemeAppCompat;
    }

    @SuppressWarnings("deprecation")
    private ILayoutPullParser getParser(ResourceReference resource) {
        ILayoutPullParser parser;
+2 −33
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import com.android.ide.common.rendering.api.HardwareConfig;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
@@ -94,7 +93,6 @@ class Layout extends RelativeLayout {
    private static final String ATTR_WINDOW_TITLE_SIZE = "windowTitleSize";
    private static final String ATTR_WINDOW_TRANSLUCENT_STATUS = StatusBar.ATTR_TRANSLUCENT;
    private static final String ATTR_WINDOW_TRANSLUCENT_NAV = NavigationBar.ATTR_TRANSLUCENT;
    private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";

    // Default sizes
    private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
@@ -236,7 +234,7 @@ class Layout extends RelativeLayout {
        boolean isMenu = "menu".equals(params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG));

        BridgeActionBar actionBar;
        if (mBuilder.isThemeAppCompat() && !isMenu) {
        if (context.isAppCompatTheme() && !isMenu) {
            actionBar = new AppCompatActionBar(context, params);
        } else {
            actionBar = new FrameworkActionBar(context, params);
@@ -324,8 +322,6 @@ class Layout extends RelativeLayout {
        private boolean mTranslucentStatus;
        private boolean mTranslucentNav;

        private Boolean mIsThemeAppCompat;

        public Builder(@NonNull SessionParams params, @NonNull BridgeContext context) {
            mParams = params;
            mContext = context;
@@ -365,7 +361,7 @@ class Layout extends RelativeLayout {
            }
            // Check if an actionbar is needed
            boolean windowActionBar = getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR,
                    !isThemeAppCompat(), true);
                    !mContext.isAppCompatTheme(), true);
            if (windowActionBar) {
                mActionBarSize = getDimension(ATTR_ACTION_BAR_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
            } else {
@@ -420,33 +416,6 @@ class Layout extends RelativeLayout {
            return mParams.getHardwareConfig().hasSoftwareButtons();
        }

        private boolean isThemeAppCompat() {
            // If a cached value exists, return it.
            if (mIsThemeAppCompat != null) {
                return mIsThemeAppCompat;
            }
            // Ideally, we should check if the corresponding activity extends
            // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
            StyleResourceValue defaultTheme = mResources.getDefaultTheme();
            // We can't simply check for parent using resources.themeIsParentOf() since the
            // inheritance structure isn't really what one would expect. The first common parent
            // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
            boolean isThemeAppCompat = false;
            for (int i = 0; i < 50; i++) {
                if (defaultTheme == null) {
                    break;
                }
                // for loop ensures that we don't run into cyclic theme inheritance.
                if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
                    isThemeAppCompat = true;
                    break;
                }
                defaultTheme = mResources.getParent(defaultTheme);
            }
            mIsThemeAppCompat = isThemeAppCompat;
            return isThemeAppCompat;
        }

        /**
         * Return true if the status bar or nav bar are present, they are not translucent (i.e
         * content doesn't overlap with them).
+1 −0
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ public final class ResourceHelper {
     */
    public static int getColor(String value) {
        if (value != null) {
            value = value.trim();
            if (!value.startsWith("#")) {
                if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) {
                    throw new NumberFormatException(String.format(