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

Commit 581fa36a authored by Diego Perez's avatar Diego Perez Committed by Android (Google) Code Review
Browse files

Merge "Add support for AppCompat widgets"

parents 56d8d05e fb175664
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -584,6 +584,7 @@ public final class BridgeTypedArray extends TypedArray {
        if (value == null) {
        if (value == null) {
            return defValue;
            return defValue;
        }
        }
        value = value.trim();


        // if the value is just an integer, return it.
        // if the value is just an integer, return it.
        try {
        try {
@@ -595,6 +596,11 @@ public final class BridgeTypedArray extends TypedArray {
            // pass
            // 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>
        // 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),
        // 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().
        // as these values can be reused internally with calls to findViewById().
+63 −19
Original line number Original line Diff line number Diff line
@@ -42,9 +42,24 @@ import android.util.AttributeSet;
import android.widget.NumberPicker;
import android.widget.NumberPicker;


import java.io.File;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
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;
import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;


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


    private final LayoutlibCallback mLayoutlibCallback;
    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 boolean mIsInMerge = false;
    private ResourceReference mResourceReference;
    private ResourceReference mResourceReference;
    private Map<View, String> mOpenDrawerLayouts;
    private Map<View, String> mOpenDrawerLayouts;
@@ -60,6 +82,15 @@ public final class BridgeInflater extends LayoutInflater {
    // Keep in sync with the same value in 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 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.
     * List of class prefixes which are tried first by default.
     * <p/>
     * <p/>
@@ -75,13 +106,15 @@ public final class BridgeInflater extends LayoutInflater {
        return sClassPrefixList;
        return sClassPrefixList;
    }
    }


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


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


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


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


            // Finally try again using the custom view loader
            // Finally try again using the custom view loader
            if (view == null) {
            if (view == null) {
+32 −0
Original line number Original line 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.
@SuppressWarnings("deprecation")  // For use of Pair.
public final class BridgeContext extends Context {
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. */
    /** The map adds cookies to each view so that IDE can link xml tags to views. */
    private final HashMap<View, Object> mViewKeyMap = new HashMap<>();
    private final HashMap<View, Object> mViewKeyMap = new HashMap<>();
@@ -153,6 +154,7 @@ public final class BridgeContext extends Context {
    private ClassLoader mClassLoader;
    private ClassLoader mClassLoader;
    private IBinder mBinder;
    private IBinder mBinder;
    private PackageManager mPackageManager;
    private PackageManager mPackageManager;
    private Boolean mIsThemeAppCompat;


    /**
    /**
     * Some applications that target both pre API 17 and post API 17, set the newer attrs to
     * 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);
        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")
    @SuppressWarnings("deprecation")
    private ILayoutPullParser getParser(ResourceReference resource) {
    private ILayoutPullParser getParser(ResourceReference resource) {
        ILayoutPullParser parser;
        ILayoutPullParser parser;
+2 −33
Original line number Original line 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.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
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.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
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_TITLE_SIZE = "windowTitleSize";
    private static final String ATTR_WINDOW_TRANSLUCENT_STATUS = StatusBar.ATTR_TRANSLUCENT;
    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 ATTR_WINDOW_TRANSLUCENT_NAV = NavigationBar.ATTR_TRANSLUCENT;
    private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";


    // Default sizes
    // Default sizes
    private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
    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));
        boolean isMenu = "menu".equals(params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG));


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


        private Boolean mIsThemeAppCompat;

        public Builder(@NonNull SessionParams params, @NonNull BridgeContext context) {
        public Builder(@NonNull SessionParams params, @NonNull BridgeContext context) {
            mParams = params;
            mParams = params;
            mContext = context;
            mContext = context;
@@ -365,7 +361,7 @@ class Layout extends RelativeLayout {
            }
            }
            // Check if an actionbar is needed
            // Check if an actionbar is needed
            boolean windowActionBar = getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR,
            boolean windowActionBar = getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR,
                    !isThemeAppCompat(), true);
                    !mContext.isAppCompatTheme(), true);
            if (windowActionBar) {
            if (windowActionBar) {
                mActionBarSize = getDimension(ATTR_ACTION_BAR_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
                mActionBarSize = getDimension(ATTR_ACTION_BAR_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
            } else {
            } else {
@@ -420,33 +416,6 @@ class Layout extends RelativeLayout {
            return mParams.getHardwareConfig().hasSoftwareButtons();
            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
         * Return true if the status bar or nav bar are present, they are not translucent (i.e
         * content doesn't overlap with them).
         * content doesn't overlap with them).
+1 −0
Original line number Original line Diff line number Diff line
@@ -77,6 +77,7 @@ public final class ResourceHelper {
     */
     */
    public static int getColor(String value) {
    public static int getColor(String value) {
        if (value != null) {
        if (value != null) {
            value = value.trim();
            if (!value.startsWith("#")) {
            if (!value.startsWith("#")) {
                if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) {
                if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) {
                    throw new NumberFormatException(String.format(
                    throw new NumberFormatException(String.format(