Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2008 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.ide.common.rendering.api.LayoutlibCallback;
     21 import com.android.ide.common.rendering.api.MergeCookie;
     22 import com.android.ide.common.rendering.api.ResourceReference;
     23 import com.android.ide.common.rendering.api.ResourceValue;
     24 import com.android.layoutlib.bridge.Bridge;
     25 import com.android.layoutlib.bridge.BridgeConstants;
     26 import com.android.layoutlib.bridge.MockView;
     27 import com.android.layoutlib.bridge.android.BridgeContext;
     28 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
     29 import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
     30 import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
     31 import com.android.layoutlib.bridge.impl.ParserFactory;
     32 import com.android.layoutlib.bridge.util.ReflectionUtils;
     33 import com.android.resources.ResourceType;
     34 import com.android.util.Pair;
     35 
     36 import org.xmlpull.v1.XmlPullParser;
     37 
     38 import android.annotation.NonNull;
     39 import android.content.Context;
     40 import android.content.res.TypedArray;
     41 import android.util.AttributeSet;
     42 
     43 import java.io.File;
     44 import java.util.HashMap;
     45 import java.util.Map;
     46 
     47 import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
     48 
     49 /**
     50  * Custom implementation of {@link LayoutInflater} to handle custom views.
     51  */
     52 public final class BridgeInflater extends LayoutInflater {
     53 
     54     private final LayoutlibCallback mLayoutlibCallback;
     55     private boolean mIsInMerge = false;
     56     private ResourceReference mResourceReference;
     57     private Map<View, String> mOpenDrawerLayouts;
     58 
     59     // Keep in sync with the same value in LayoutInflater.
     60     private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme };
     61 
     62     /**
     63      * List of class prefixes which are tried first by default.
     64      * <p/>
     65      * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater.
     66      */
     67     private static final String[] sClassPrefixList = {
     68         "android.widget.",
     69         "android.webkit.",
     70         "android.app."
     71     };
     72 
     73     public static String[] getClassPrefixList() {
     74         return sClassPrefixList;
     75     }
     76 
     77     protected BridgeInflater(LayoutInflater original, Context newContext) {
     78         super(original, newContext);
     79         newContext = getBaseContext(newContext);
     80         if (newContext instanceof BridgeContext) {
     81             mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback();
     82         } else {
     83             mLayoutlibCallback = null;
     84         }
     85     }
     86 
     87     /**
     88      * Instantiate a new BridgeInflater with an {@link LayoutlibCallback} object.
     89      *
     90      * @param context The Android application context.
     91      * @param layoutlibCallback the {@link LayoutlibCallback} object.
     92      */
     93     public BridgeInflater(Context context, LayoutlibCallback layoutlibCallback) {
     94         super(context);
     95         mLayoutlibCallback = layoutlibCallback;
     96         mConstructorArgs[0] = context;
     97     }
     98 
     99     @Override
    100     public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
    101         View view = null;
    102 
    103         try {
    104             // First try to find a class using the default Android prefixes
    105             for (String prefix : sClassPrefixList) {
    106                 try {
    107                     view = createView(name, prefix, attrs);
    108                     if (view != null) {
    109                         break;
    110                     }
    111                 } catch (ClassNotFoundException e) {
    112                     // Ignore. We'll try again using the base class below.
    113                 }
    114             }
    115 
    116             // Next try using the parent loader. This will most likely only work for
    117             // fully-qualified class names.
    118             try {
    119                 if (view == null) {
    120                     view = super.onCreateView(name, attrs);
    121                 }
    122             } catch (ClassNotFoundException e) {
    123                 // Ignore. We'll try again using the custom view loader below.
    124             }
    125 
    126             // Finally try again using the custom view loader
    127             if (view == null) {
    128                 view = loadCustomView(name, attrs);
    129             }
    130         } catch (InflateException e) {
    131             // Don't catch the InflateException below as that results in hiding the real cause.
    132             throw e;
    133         } catch (Exception e) {
    134             // Wrap the real exception in a ClassNotFoundException, so that the calling method
    135             // can deal with it.
    136             throw new ClassNotFoundException("onCreateView", e);
    137         }
    138 
    139         setupViewInContext(view, attrs);
    140 
    141         return view;
    142     }
    143 
    144     @Override
    145     public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
    146             boolean ignoreThemeAttr) {
    147         View view;
    148         try {
    149             view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr);
    150         } catch (InflateException e) {
    151             // Creation of ContextThemeWrapper code is same as in the super method.
    152             // Apply a theme wrapper, if allowed and one is specified.
    153             if (!ignoreThemeAttr) {
    154                 final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
    155                 final int themeResId = ta.getResourceId(0, 0);
    156                 if (themeResId != 0) {
    157                     context = new ContextThemeWrapper(context, themeResId);
    158                 }
    159                 ta.recycle();
    160             }
    161             if (!(e.getCause() instanceof ClassNotFoundException)) {
    162                 // There is some unknown inflation exception in inflating a View that was found.
    163                 view = new MockView(context, attrs);
    164                 ((MockView) view).setText(name);
    165                 Bridge.getLog().error(LayoutLog.TAG_BROKEN, e.getMessage(), e, null);
    166             } else {
    167                 final Object lastContext = mConstructorArgs[0];
    168                 mConstructorArgs[0] = context;
    169                 // try to load the class from using the custom view loader
    170                 try {
    171                     view = loadCustomView(name, attrs);
    172                 } catch (Exception e2) {
    173                     // Wrap the real exception in an InflateException so that the calling
    174                     // method can deal with it.
    175                     InflateException exception = new InflateException();
    176                     if (!e2.getClass().equals(ClassNotFoundException.class)) {
    177                         exception.initCause(e2);
    178                     } else {
    179                         exception.initCause(e);
    180                     }
    181                     throw exception;
    182                 } finally {
    183                     mConstructorArgs[0] = lastContext;
    184                 }
    185             }
    186         }
    187 
    188         setupViewInContext(view, attrs);
    189 
    190         return view;
    191     }
    192 
    193     @Override
    194     public View inflate(int resource, ViewGroup root) {
    195         Context context = getContext();
    196         context = getBaseContext(context);
    197         if (context instanceof BridgeContext) {
    198             BridgeContext bridgeContext = (BridgeContext)context;
    199 
    200             ResourceValue value = null;
    201 
    202             @SuppressWarnings("deprecation")
    203             Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
    204             if (layoutInfo != null) {
    205                 value = bridgeContext.getRenderResources().getFrameworkResource(
    206                         ResourceType.LAYOUT, layoutInfo.getSecond());
    207             } else {
    208                 layoutInfo = mLayoutlibCallback.resolveResourceId(resource);
    209 
    210                 if (layoutInfo != null) {
    211                     value = bridgeContext.getRenderResources().getProjectResource(
    212                             ResourceType.LAYOUT, layoutInfo.getSecond());
    213                 }
    214             }
    215 
    216             if (value != null) {
    217                 File f = new File(value.getValue());
    218                 if (f.isFile()) {
    219                     try {
    220                         XmlPullParser parser = ParserFactory.create(f, true);
    221 
    222                         BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
    223                                 parser, bridgeContext, value.isFramework());
    224 
    225                         return inflate(bridgeParser, root);
    226                     } catch (Exception e) {
    227                         Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
    228                                 "Failed to parse file " + f.getAbsolutePath(), e, null);
    229 
    230                         return null;
    231                     }
    232                 }
    233             }
    234         }
    235         return null;
    236     }
    237 
    238     private View loadCustomView(String name, AttributeSet attrs) throws Exception {
    239         if (mLayoutlibCallback != null) {
    240             // first get the classname in case it's not the node name
    241             if (name.equals("view")) {
    242                 name = attrs.getAttributeValue(null, "class");
    243             }
    244 
    245             mConstructorArgs[1] = attrs;
    246 
    247             Object customView = mLayoutlibCallback.loadView(name, mConstructorSignature,
    248                     mConstructorArgs);
    249 
    250             if (customView instanceof View) {
    251                 return (View)customView;
    252             }
    253         }
    254 
    255         return null;
    256     }
    257 
    258     private void setupViewInContext(View view, AttributeSet attrs) {
    259         Context context = getContext();
    260         context = getBaseContext(context);
    261         if (context instanceof BridgeContext) {
    262             BridgeContext bc = (BridgeContext) context;
    263             // get the view key
    264             Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
    265             if (viewKey != null) {
    266                 bc.addViewKey(view, viewKey);
    267             }
    268             String scrollPosX = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollX");
    269             if (scrollPosX != null && scrollPosX.endsWith("px")) {
    270                 int value = Integer.parseInt(scrollPosX.substring(0, scrollPosX.length() - 2));
    271                 bc.setScrollXPos(view, value);
    272             }
    273             String scrollPosY = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
    274             if (scrollPosY != null && scrollPosY.endsWith("px")) {
    275                 int value = Integer.parseInt(scrollPosY.substring(0, scrollPosY.length() - 2));
    276                 bc.setScrollYPos(view, value);
    277             }
    278             if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
    279                 Integer resourceId = null;
    280                 String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
    281                         BridgeConstants.ATTR_LIST_ITEM);
    282                 if (attrVal != null && !attrVal.isEmpty()) {
    283                     ResourceValue resValue = bc.getRenderResources().findResValue(attrVal, false);
    284                     if (resValue.isFramework()) {
    285                         resourceId = Bridge.getResourceId(resValue.getResourceType(),
    286                                 resValue.getName());
    287                     } else {
    288                         resourceId = mLayoutlibCallback.getResourceId(resValue.getResourceType(),
    289                                 resValue.getName());
    290                     }
    291                 }
    292                 if (resourceId == null) {
    293                     resourceId = 0;
    294                 }
    295                 RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId);
    296             } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) {
    297                 String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
    298                         BridgeConstants.ATTR_OPEN_DRAWER);
    299                 if (attrVal != null) {
    300                     getDrawerLayoutMap().put(view, attrVal);
    301                 }
    302             }
    303 
    304         }
    305     }
    306 
    307     public void setIsInMerge(boolean isInMerge) {
    308         mIsInMerge = isInMerge;
    309     }
    310 
    311     public void setResourceReference(ResourceReference reference) {
    312         mResourceReference = reference;
    313     }
    314 
    315     @Override
    316     public LayoutInflater cloneInContext(Context newContext) {
    317         return new BridgeInflater(this, newContext);
    318     }
    319 
    320     /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc,
    321             ResourceReference resourceReference, boolean isInMerge) {
    322 
    323         if (!(attrs instanceof BridgeXmlBlockParser)) {
    324             return null;
    325         }
    326         BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs);
    327 
    328         // get the view key
    329         Object viewKey = parser.getViewCookie();
    330 
    331         if (viewKey == null) {
    332             int currentDepth = parser.getDepth();
    333 
    334             // test whether we are in an included file or in a adapter binding view.
    335             BridgeXmlBlockParser previousParser = bc.getPreviousParser();
    336             if (previousParser != null) {
    337                 // looks like we are inside an embedded layout.
    338                 // only apply the cookie of the calling node (<include>) if we are at the
    339                 // top level of the embedded layout. If there is a merge tag, then
    340                 // skip it and look for the 2nd level
    341                 int testDepth = isInMerge ? 2 : 1;
    342                 if (currentDepth == testDepth) {
    343                     viewKey = previousParser.getViewCookie();
    344                     // if we are in a merge, wrap the cookie in a MergeCookie.
    345                     if (viewKey != null && isInMerge) {
    346                         viewKey = new MergeCookie(viewKey);
    347                     }
    348                 }
    349             } else if (resourceReference != null && currentDepth == 1) {
    350                 // else if there's a resource reference, this means we are in an adapter
    351                 // binding case. Set the resource ref as the view cookie only for the top
    352                 // level view.
    353                 viewKey = resourceReference;
    354             }
    355         }
    356 
    357         return viewKey;
    358     }
    359 
    360     public void postInflateProcess(View view) {
    361         if (mOpenDrawerLayouts != null) {
    362             String gravity = mOpenDrawerLayouts.get(view);
    363             if (gravity != null) {
    364                 DrawerLayoutUtil.openDrawer(view, gravity);
    365             }
    366             mOpenDrawerLayouts.remove(view);
    367         }
    368     }
    369 
    370     @NonNull
    371     private Map<View, String> getDrawerLayoutMap() {
    372         if (mOpenDrawerLayouts == null) {
    373             mOpenDrawerLayouts = new HashMap<View, String>(4);
    374         }
    375         return mOpenDrawerLayouts;
    376     }
    377 
    378     public void onDoneInflation() {
    379         if (mOpenDrawerLayouts != null) {
    380             mOpenDrawerLayouts.clear();
    381         }
    382     }
    383 }
    384