Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.view;
     18 
     19 import com.android.ide.common.rendering.api.LayoutLog;
     20 import com.android.layoutlib.bridge.Bridge;
     21 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
     22 
     23 import org.xmlpull.v1.XmlPullParser;
     24 import org.xmlpull.v1.XmlPullParserException;
     25 
     26 import android.content.Context;
     27 import android.content.res.TypedArray;
     28 import android.content.res.XmlResourceParser;
     29 import android.util.AttributeSet;
     30 import android.util.TypedValue;
     31 import android.util.Xml;
     32 
     33 import java.io.IOException;
     34 
     35 /**
     36  * Delegate used to provide new implementation of a select few methods of {@link LayoutInflater}
     37  *
     38  * Through the layoutlib_create tool, the original  methods of LayoutInflater have been replaced
     39  * by calls to methods of the same name in this delegate class.
     40  *
     41  */
     42 public class LayoutInflater_Delegate {
     43     private static final String TAG_MERGE = "merge";
     44 
     45     private static final String ATTR_LAYOUT = "layout";
     46 
     47     private static final int[] ATTRS_THEME = new int[] {
     48             com.android.internal.R.attr.theme };
     49 
     50     public static boolean sIsInInclude = false;
     51 
     52     /**
     53      * Recursive method used to descend down the xml hierarchy and instantiate
     54      * views, instantiate their children, and then call onFinishInflate().
     55      *
     56      * This implementation just records the merge status before calling the default implementation.
     57      */
     58     @LayoutlibDelegate
     59     /* package */ static void rInflate(LayoutInflater thisInflater, XmlPullParser parser,
     60             View parent, Context context, AttributeSet attrs, boolean finishInflate)
     61             throws XmlPullParserException, IOException {
     62 
     63         if (finishInflate == false) {
     64             // this is a merge rInflate!
     65             if (thisInflater instanceof BridgeInflater) {
     66                 ((BridgeInflater) thisInflater).setIsInMerge(true);
     67             }
     68         }
     69 
     70         // ---- START DEFAULT IMPLEMENTATION.
     71 
     72         thisInflater.rInflate_Original(parser, parent, context, attrs, finishInflate);
     73 
     74         // ---- END DEFAULT IMPLEMENTATION.
     75 
     76         if (finishInflate == false) {
     77             // this is a merge rInflate!
     78             if (thisInflater instanceof BridgeInflater) {
     79                 ((BridgeInflater) thisInflater).setIsInMerge(false);
     80             }
     81         }
     82     }
     83 
     84     @LayoutlibDelegate
     85     public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser,
     86             Context context, View parent, AttributeSet attrs)
     87             throws XmlPullParserException, IOException {
     88         int type;
     89 
     90         if (parent instanceof ViewGroup) {
     91             // Apply a theme wrapper, if requested. This is sort of a weird
     92             // edge case, since developers think the <include> overwrites
     93             // values in the AttributeSet of the included View. So, if the
     94             // included View has a theme attribute, we'll need to ignore it.
     95             final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
     96             final int themeResId = ta.getResourceId(0, 0);
     97             final boolean hasThemeOverride = themeResId != 0;
     98             if (hasThemeOverride) {
     99                 context = new ContextThemeWrapper(context, themeResId);
    100             }
    101             ta.recycle();
    102 
    103             // If the layout is pointing to a theme attribute, we have to
    104             // massage the value to get a resource identifier out of it.
    105             int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
    106             if (layout == 0) {
    107                 final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
    108                 if (value == null || value.length() <= 0) {
    109                     Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a layout in the"
    110                             + " include tag: <include layout=\"@layout/layoutID\" />", null);
    111                     LayoutInflater.consumeChildElements(parser);
    112                     return;
    113                 }
    114 
    115                 // Attempt to resolve the "?attr/name" string to an identifier.
    116                 layout = context.getResources().getIdentifier(value.substring(1), null, null);
    117             }
    118 
    119             // The layout might be referencing a theme attribute.
    120             // ---- START CHANGES
    121             if (layout != 0) {
    122                 final TypedValue tempValue = new TypedValue();
    123                 if (context.getTheme().resolveAttribute(layout, tempValue, true)) {
    124                     layout = tempValue.resourceId;
    125                 }
    126             }
    127             // ---- END CHANGES
    128 
    129             if (layout == 0) {
    130                 final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
    131                 if (value == null) {
    132                     Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a layout in the"
    133                             + " include tag: <include layout=\"@layout/layoutID\" />", null);
    134                 } else {
    135                     Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a valid layout "
    136                             + "reference. The layout ID " + value + " is not valid.", null);
    137                 }
    138             } else {
    139                 final XmlResourceParser childParser =
    140                     thisInflater.getContext().getResources().getLayout(layout);
    141 
    142                 try {
    143                     final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
    144 
    145                     while ((type = childParser.next()) != XmlPullParser.START_TAG &&
    146                             type != XmlPullParser.END_DOCUMENT) {
    147                         // Empty.
    148                     }
    149 
    150                     if (type != XmlPullParser.START_TAG) {
    151                         Bridge.getLog().error(LayoutLog.TAG_BROKEN,
    152                                 childParser.getPositionDescription() + ": No start tag found!",
    153                                 null);
    154                         LayoutInflater.consumeChildElements(parser);
    155                         return;
    156                     }
    157 
    158                     final String childName = childParser.getName();
    159 
    160                     if (TAG_MERGE.equals(childName)) {
    161                         // Inflate all children.
    162                         thisInflater.rInflate(childParser, parent, context, childAttrs, false);
    163                     } else {
    164                         final View view = thisInflater.createViewFromTag(parent, childName,
    165                                 context, childAttrs, hasThemeOverride);
    166                         final ViewGroup group = (ViewGroup) parent;
    167 
    168                         final TypedArray a = context.obtainStyledAttributes(
    169                                 attrs, com.android.internal.R.styleable.Include);
    170                         final int id = a.getResourceId(
    171                                 com.android.internal.R.styleable.Include_id, View.NO_ID);
    172                         final int visibility = a.getInt(
    173                                 com.android.internal.R.styleable.Include_visibility, -1);
    174                         a.recycle();
    175 
    176                         // We try to load the layout params set in the <include /> tag. If
    177                         // they don't exist, we will rely on the layout params set in the
    178                         // included XML file.
    179                         // During a layoutparams generation, a runtime exception is thrown
    180                         // if either layout_width or layout_height is missing. We catch
    181                         // this exception and set localParams accordingly: true means we
    182                         // successfully loaded layout params from the <include /> tag,
    183                         // false means we need to rely on the included layout params.
    184                         ViewGroup.LayoutParams params = null;
    185                         try {
    186                             // ---- START CHANGES
    187                             sIsInInclude = true;
    188                             // ---- END CHANGES
    189 
    190                             params = group.generateLayoutParams(attrs);
    191                         } catch (RuntimeException ignored) {
    192                             // Ignore, just fail over to child attrs.
    193                         } finally {
    194                             // ---- START CHANGES
    195                             sIsInInclude = false;
    196                             // ---- END CHANGES
    197                         }
    198                         if (params == null) {
    199                             params = group.generateLayoutParams(childAttrs);
    200                         }
    201                         view.setLayoutParams(params);
    202 
    203                         // Inflate all children.
    204                         thisInflater.rInflateChildren(childParser, view, childAttrs, true);
    205 
    206                         if (id != View.NO_ID) {
    207                             view.setId(id);
    208                         }
    209 
    210                         switch (visibility) {
    211                             case 0:
    212                                 view.setVisibility(View.VISIBLE);
    213                                 break;
    214                             case 1:
    215                                 view.setVisibility(View.INVISIBLE);
    216                                 break;
    217                             case 2:
    218                                 view.setVisibility(View.GONE);
    219                                 break;
    220                         }
    221 
    222                         group.addView(view);
    223                     }
    224                 } finally {
    225                     childParser.close();
    226                 }
    227             }
    228         } else {
    229             Bridge.getLog().error(LayoutLog.TAG_BROKEN,
    230                     "<include /> can only be used inside of a ViewGroup",
    231                     null);
    232         }
    233 
    234         LayoutInflater.consumeChildElements(parser);
    235     }
    236 }
    237