Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2009 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.cts;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertFalse;
     21 import static org.junit.Assert.assertNotNull;
     22 import static org.junit.Assert.assertNotSame;
     23 import static org.junit.Assert.assertNull;
     24 import static org.junit.Assert.assertSame;
     25 import static org.junit.Assert.assertTrue;
     26 import static org.junit.Assert.fail;
     27 
     28 import android.content.ComponentName;
     29 import android.content.Context;
     30 import android.content.ContextWrapper;
     31 import android.content.pm.ActivityInfo;
     32 import android.content.pm.PackageManager;
     33 import android.content.res.Resources;
     34 import android.content.res.Resources.Theme;
     35 import android.content.res.XmlResourceParser;
     36 import android.util.AttributeSet;
     37 import android.util.TypedValue;
     38 import android.util.Xml;
     39 import android.view.ContextThemeWrapper;
     40 import android.view.Gravity;
     41 import android.view.InflateException;
     42 import android.view.LayoutInflater;
     43 import android.view.LayoutInflater.Factory;
     44 import android.view.LayoutInflater.Filter;
     45 import android.view.View;
     46 import android.view.ViewGroup;
     47 import android.view.cts.util.XmlUtils;
     48 import android.widget.LinearLayout;
     49 
     50 import androidx.test.InstrumentationRegistry;
     51 import androidx.test.filters.SmallTest;
     52 import androidx.test.runner.AndroidJUnit4;
     53 
     54 import org.junit.Before;
     55 import org.junit.Test;
     56 import org.junit.runner.RunWith;
     57 import org.xmlpull.v1.XmlPullParser;
     58 
     59 import java.util.ArrayList;
     60 import java.util.List;
     61 
     62 @SmallTest
     63 @RunWith(AndroidJUnit4.class)
     64 public class LayoutInflaterTest {
     65     private LayoutInflater mLayoutInflater;
     66 
     67     private Context mContext;
     68 
     69     private final Factory mFactory = (String name, Context context, AttributeSet attrs) -> null;
     70 
     71     private boolean isOnLoadClass;
     72 
     73     private final Filter mFilter = (Class clazz) -> {
     74         isOnLoadClass = true;
     75         return true;
     76     };
     77 
     78     @Before
     79     public void setup() {
     80         mContext = InstrumentationRegistry.getTargetContext();
     81         mLayoutInflater = (LayoutInflater) mContext.getSystemService(
     82                 Context.LAYOUT_INFLATER_SERVICE);
     83     }
     84 
     85     @Test
     86     public void testFrom() {
     87         mLayoutInflater = null;
     88         mLayoutInflater = LayoutInflater.from(mContext);
     89         assertNotNull(mLayoutInflater);
     90         mLayoutInflater = null;
     91         mLayoutInflater = new MockLayoutInflater(mContext);
     92         assertNotNull(mLayoutInflater);
     93 
     94         LayoutInflater layoutInflater = new MockLayoutInflater(mLayoutInflater,
     95                 mContext);
     96         assertNotNull(layoutInflater);
     97     }
     98 
     99     @Test
    100     public void testAccessLayoutInflaterProperties() {
    101         mLayoutInflater.setFilter(mFilter);
    102         assertSame(mFilter, mLayoutInflater.getFilter());
    103         mLayoutInflater.setFactory(mFactory);
    104         assertSame(mFactory, mLayoutInflater.getFactory());
    105         mLayoutInflater = new MockLayoutInflater(mContext);
    106         assertSame(mContext, mLayoutInflater.getContext());
    107     }
    108 
    109     private AttributeSet getAttrs() {
    110         XmlResourceParser parser = null;
    111         AttributeSet attrs = null;
    112         ActivityInfo ai = null;
    113         ComponentName mComponentName = new ComponentName(mContext,
    114                 MockActivity.class);
    115         try {
    116             ai = mContext.getPackageManager().getActivityInfo(mComponentName,
    117                     PackageManager.GET_META_DATA);
    118             parser = ai.loadXmlMetaData(mContext.getPackageManager(),
    119                     "android.widget.layout");
    120             int type;
    121             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    122                     && type != XmlPullParser.START_TAG) {
    123             }
    124             String nodeName = parser.getName();
    125             if (!"alias".equals(nodeName)) {
    126                 throw new InflateException();
    127             }
    128             int outerDepth = parser.getDepth();
    129             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    130                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    131                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    132                     continue;
    133                 }
    134                 nodeName = parser.getName();
    135                 if ("AbsoluteLayout".equals(nodeName)) {
    136                     attrs = Xml.asAttributeSet(parser);
    137                 } else {
    138                     XmlUtils.skipCurrentTag(parser);
    139                 }
    140             }
    141         } catch (Exception e) {
    142         }
    143         return attrs;
    144     }
    145 
    146     @Test
    147     public void testCreateView() {
    148         AttributeSet attrs = getAttrs();
    149         isOnLoadClass = false;
    150         View view = null;
    151         try {
    152             view = mLayoutInflater.createView("testthrow", "com.android", attrs);
    153             fail("should throw exception");
    154         } catch (InflateException e) {
    155         } catch (ClassNotFoundException e) {
    156         }
    157         assertFalse(isOnLoadClass);
    158         assertNull(view);
    159         mLayoutInflater = null;
    160         mLayoutInflater = LayoutInflater.from(mContext);
    161         isOnLoadClass = false;
    162         mLayoutInflater.setFilter((Class clazz) -> {
    163             isOnLoadClass = true;
    164             return false;
    165         });
    166         try {
    167             view = mLayoutInflater.createView("MockActivity",
    168                     "com.android.app.", attrs);
    169             fail("should throw exception");
    170         } catch (InflateException e) {
    171         } catch (ClassNotFoundException e) {
    172         }
    173         assertFalse(isOnLoadClass);
    174         assertNull(view);
    175 
    176         isOnLoadClass = false;
    177         // allowedState is false
    178         try {
    179             view = mLayoutInflater.createView(MockActivity.class.getName(),
    180                     MockActivity.class.getPackage().toString(), attrs);
    181             fail("should throw exception");
    182         } catch (InflateException e) {
    183         } catch (ClassNotFoundException e) {
    184         }
    185         assertFalse(isOnLoadClass);
    186         assertNull(view);
    187         mLayoutInflater = null;
    188         mLayoutInflater = LayoutInflater.from(mContext);
    189         try {
    190             mLayoutInflater.setFilter(null);
    191             view = mLayoutInflater.createView("com.android.app.MockActivity",
    192                     null, attrs);
    193             assertNotNull(view);
    194             assertFalse(isOnLoadClass);
    195             mLayoutInflater = null;
    196             mLayoutInflater = LayoutInflater.from(mContext);
    197             mLayoutInflater.setFilter(null);
    198 
    199             view = mLayoutInflater.createView(MockActivity.class.getName(),
    200                     MockActivity.class.getPackage().toString(), attrs);
    201             assertNotNull(view);
    202             assertFalse(isOnLoadClass);
    203             mLayoutInflater.setFilter(mFilter);
    204             view = mLayoutInflater.createView(MockActivity.class.getName(),
    205                     MockActivity.class.getPackage().toString(), attrs);
    206             assertNotNull(view);
    207             assertTrue(isOnLoadClass);
    208             // allowedState!=null
    209             view = mLayoutInflater.createView(MockActivity.class.getName(),
    210                     MockActivity.class.getPackage().toString(), attrs);
    211             assertNotNull(view);
    212             assertTrue(isOnLoadClass);
    213         } catch (InflateException e) {
    214         } catch (ClassNotFoundException e) {
    215         }
    216     }
    217 
    218     @Test(expected=Resources.NotFoundException.class)
    219     public void testInflateInvalidId() {
    220         mLayoutInflater.inflate(-1, null);
    221     }
    222 
    223     @Test
    224     public void testInflate() {
    225         View view = mLayoutInflater.inflate(
    226                 android.view.cts.R.layout.inflater_layout, null);
    227         assertNotNull(view);
    228 
    229         LinearLayout linearLayout = new LinearLayout(mContext);
    230         linearLayout.setOrientation(LinearLayout.VERTICAL);
    231         linearLayout.setHorizontalGravity(Gravity.LEFT);
    232         linearLayout.setLayoutParams(new ViewGroup.LayoutParams(
    233                 ViewGroup.LayoutParams.MATCH_PARENT,
    234                 ViewGroup.LayoutParams.MATCH_PARENT));
    235         assertEquals(0, linearLayout.getChildCount());
    236         view = mLayoutInflater.inflate(R.layout.inflater_layout,
    237                 linearLayout);
    238         assertNotNull(view);
    239         assertEquals(1, linearLayout.getChildCount());
    240     }
    241 
    242     @Test(expected=Resources.NotFoundException.class)
    243     public void testInflateAttachToRootInvalidId() {
    244         mLayoutInflater.inflate(-1, null, false);
    245     }
    246 
    247     @Test
    248     public void testInflateAttachToRoot() {
    249         View view = mLayoutInflater.inflate(
    250                 R.layout.inflater_layout, null, false);
    251         assertNotNull(view);
    252 
    253         LinearLayout linearLayout = new LinearLayout(mContext);
    254         linearLayout.setOrientation(LinearLayout.VERTICAL);
    255         linearLayout.setHorizontalGravity(Gravity.LEFT);
    256         linearLayout.setLayoutParams(new ViewGroup.LayoutParams(
    257                 ViewGroup.LayoutParams.MATCH_PARENT,
    258                 ViewGroup.LayoutParams.MATCH_PARENT));
    259         assertEquals(0, linearLayout.getChildCount());
    260         view = mLayoutInflater.inflate(R.layout.inflater_layout,
    261                 linearLayout, false);
    262         assertNotNull(view);
    263         assertEquals(0, linearLayout.getChildCount());
    264 
    265         view = mLayoutInflater.inflate(R.layout.inflater_layout,
    266                 linearLayout, true);
    267         assertNotNull(view);
    268         assertEquals(1, linearLayout.getChildCount());
    269     }
    270 
    271     @Test(expected=NullPointerException.class)
    272     public void testInflateParserNullParser() {
    273         mLayoutInflater.inflate(null, null);
    274     }
    275 
    276     @Test
    277     public void testInflateParser() {
    278         XmlResourceParser parser = mContext.getResources().getLayout(R.layout.inflater_layout);
    279         View view = mLayoutInflater.inflate(parser, null);
    280         assertNotNull(view);
    281 
    282         LinearLayout linearLayout = new LinearLayout(mContext);
    283         linearLayout.setOrientation(LinearLayout.VERTICAL);
    284         linearLayout.setHorizontalGravity(Gravity.LEFT);
    285         linearLayout.setLayoutParams(new ViewGroup.LayoutParams(
    286                 ViewGroup.LayoutParams.MATCH_PARENT,
    287                 ViewGroup.LayoutParams.MATCH_PARENT));
    288         assertEquals(0, linearLayout.getChildCount());
    289 
    290         try {
    291             mLayoutInflater.inflate(parser, linearLayout);
    292             fail("should throw exception");
    293         } catch (NullPointerException e) {
    294         }
    295 
    296         parser = mContext.getResources().getLayout(R.layout.inflater_layout);
    297         view = mLayoutInflater.inflate(parser, linearLayout);
    298         assertNotNull(view);
    299         assertEquals(1, linearLayout.getChildCount());
    300         parser = mContext.getResources().getLayout(R.layout.inflater_layout);
    301         view = mLayoutInflater.inflate(parser, linearLayout);
    302         assertNotNull(view);
    303         assertEquals(2, linearLayout.getChildCount());
    304 
    305         parser = getParser();
    306         view = mLayoutInflater.inflate(parser, linearLayout);
    307         assertNotNull(view);
    308         assertEquals(3, linearLayout.getChildCount());
    309     }
    310 
    311     @Test(expected=NullPointerException.class)
    312     public void testInflateParserAttachToRootNullParser() {
    313         mLayoutInflater.inflate(null, null, false);
    314     }
    315 
    316     @Test
    317     public void testInflateParserAttachToRoot() {
    318         XmlResourceParser parser = mContext.getResources().getLayout(R.layout.inflater_layout);
    319         View view = mLayoutInflater.inflate(parser, null, false);
    320         assertNotNull(view);
    321 
    322         LinearLayout linearLayout = new LinearLayout(mContext);
    323         linearLayout.setOrientation(LinearLayout.VERTICAL);
    324         linearLayout.setHorizontalGravity(Gravity.LEFT);
    325         linearLayout.setLayoutParams(new ViewGroup.LayoutParams(
    326                 ViewGroup.LayoutParams.MATCH_PARENT,
    327                 ViewGroup.LayoutParams.MATCH_PARENT));
    328         assertEquals(0, linearLayout.getChildCount());
    329 
    330         try {
    331             mLayoutInflater.inflate(parser, linearLayout, false);
    332             fail("should throw exception");
    333         } catch (NullPointerException e) {
    334         }
    335 
    336         parser = mContext.getResources().getLayout(R.layout.inflater_layout);
    337         view = mLayoutInflater.inflate(parser, linearLayout, false);
    338         assertNull(view.getParent());
    339         assertNotNull(view);
    340         assertEquals(0, linearLayout.getChildCount());
    341         parser = mContext.getResources().getLayout(R.layout.inflater_layout);
    342         assertEquals(0, linearLayout.getChildCount());
    343         view = mLayoutInflater.inflate(parser, linearLayout, true);
    344         assertNotNull(view);
    345         assertNull(view.getParent());
    346         assertEquals(1, linearLayout.getChildCount());
    347 
    348         parser = getParser();
    349         try {
    350             mLayoutInflater.inflate(parser, linearLayout, false);
    351             fail("should throw exception");
    352         } catch (InflateException e) {
    353         }
    354 
    355         parser = getParser();
    356 
    357         view = mLayoutInflater.inflate(parser, linearLayout, true);
    358         assertNotNull(view);
    359         assertEquals(2, linearLayout.getChildCount());
    360     }
    361 
    362     @Test
    363     public void testOverrideTheme() {
    364         View container = mLayoutInflater.inflate(R.layout.inflater_override_theme_layout, null);
    365         verifyThemeType(container, "view_outer", R.id.view_outer, 1);
    366         verifyThemeType(container, "view_inner", R.id.view_inner, 2);
    367         verifyThemeType(container, "view_attr", R.id.view_attr, 3);
    368         verifyThemeType(container, "view_include", R.id.view_include, 4);
    369         verifyThemeType(container, "view_include_notheme", R.id.view_include_notheme, 5);
    370     }
    371 
    372     private void verifyThemeType(View container, String tag, int id, int type) {
    373         TypedValue outValue = new TypedValue();
    374         View view = container.findViewById(id);
    375         assertNotNull("Found " + tag, view);
    376         Theme theme = view.getContext().getTheme();
    377         boolean resolved = theme.resolveAttribute(R.attr.themeType, outValue, true);
    378         assertTrue("Resolved themeType for " + tag, resolved);
    379         assertEquals(tag + " has themeType " + type, type, outValue.data);
    380     }
    381 
    382     @Test
    383     public void testInflateTags() {
    384         final View view = mLayoutInflater.inflate(
    385                 android.view.cts.R.layout.inflater_layout_tags, null);
    386         assertNotNull(view);
    387 
    388         checkViewTag(view, R.id.viewlayout_root, R.id.tag_viewlayout_root, R.string.tag1);
    389         checkViewTag(view, R.id.mock_view, R.id.tag_mock_view, R.string.tag2);
    390     }
    391 
    392     private void checkViewTag(View parent, int viewId, int tagId, int valueResId) {
    393         final View target = parent.findViewById(viewId);
    394         assertNotNull("Found target view for " + viewId, target);
    395 
    396         final Object tag = target.getTag(tagId);
    397         assertNotNull("Tag is set", tag);
    398         assertTrue("Tag is a character sequence", tag instanceof CharSequence);
    399 
    400         final Context targetContext = target.getContext();
    401         final CharSequence expectedValue = targetContext.getString(valueResId);
    402         assertEquals(tagId + " has tag " + expectedValue, expectedValue, tag);
    403     }
    404 
    405     @Test
    406     public void testInclude() {
    407         final Context themedContext = new ContextThemeWrapper(mContext, R.style.Test_Theme);
    408         final LayoutInflater inflater = LayoutInflater.from(themedContext);
    409 
    410         final View container = inflater.inflate(R.layout.inflater_layout_include, null);
    411         assertNotNull(container.findViewById(R.id.include_layout_explicit));
    412         assertNotNull(container.findViewById(R.id.include_layout_from_attr));
    413     }
    414 
    415     @Test(expected = InflateException.class)
    416     public void testIncludeMissingAttr() {
    417         final View container = mLayoutInflater.inflate(R.layout.inflater_layout_include, null);
    418         assertNotNull(container.findViewById(R.id.include_layout_from_attr));
    419     }
    420 
    421     // API : LayoutInflater#onCreateView(Context, View, String, AttributeSet)
    422     // API : LayoutInflater#createView(Context, String, prefix, AttributeSet)
    423     // onCreateView with Context should be called when there is no factory to inflate and should
    424     // pass the LayoutInflater's context at the root and the parent's context other times.
    425     // createView with Context should be called when there is a "." in the name.
    426     @Test
    427     public void testCreateViewWithContext() throws Throwable {
    428         final Context context = mLayoutInflater.getContext();
    429         CreateViewContextInflater inflater = new CreateViewContextInflater(context);
    430         final ViewGroup container = (ViewGroup) inflater.inflate(R.layout.inflater_layout, null);
    431 
    432         // The LinearLayout and TextViews inflate with onCreateView
    433         assertEquals(9, inflater.onCreateViewCalls.size());
    434         assertEquals(context, inflater.onCreateViewCalls.get(0)[0]);
    435         assertNull(inflater.onCreateViewCalls.get(0)[1]);
    436         assertEquals("LinearLayout", inflater.onCreateViewCalls.get(0)[2]);
    437 
    438         Context parentContext = container.getContext();
    439         // This should be a wrapper context since MethodTrackerInflater changes LinearLayout context
    440         assertNotSame(context, parentContext);
    441         for (int i = 1; i < 9; i++) {
    442             Object[] params = inflater.onCreateViewCalls.get(i);
    443             assertEquals(parentContext, params[0]);
    444             assertEquals(container, params[1]);
    445             assertEquals("TextView", params[2]);
    446         }
    447 
    448         // Also has android.view.ct.MockViewStub
    449         assertEquals(9, container.getChildCount());
    450         View mockViewStub = container.getChildAt(8);
    451         assertEquals(parentContext, mockViewStub.getContext());
    452     }
    453 
    454     static class MockLayoutInflater extends LayoutInflater {
    455 
    456         public MockLayoutInflater(Context c) {
    457             super(c);
    458         }
    459 
    460         public MockLayoutInflater(LayoutInflater original, Context newContext) {
    461             super(original, newContext);
    462         }
    463 
    464         @Override
    465         public View onCreateView(String name, AttributeSet attrs)
    466                 throws ClassNotFoundException {
    467             return super.onCreateView(name, attrs);
    468         }
    469 
    470         @Override
    471         public LayoutInflater cloneInContext(Context newContext) {
    472             return null;
    473         }
    474     }
    475 
    476     /**
    477      * Used to track calling of onCreateView protected method.
    478      */
    479     static class CreateViewContextInflater extends LayoutInflater {
    480         public final List<Object[]> onCreateViewCalls = new ArrayList<>();
    481 
    482         CreateViewContextInflater(Context context) {
    483             super(context);
    484         }
    485 
    486         @Override
    487         public LayoutInflater cloneInContext(Context newContext) {
    488             return null;
    489         }
    490 
    491         @Override
    492         public View onCreateView(Context viewContext, View parent, String name,
    493                 AttributeSet attrs) throws ClassNotFoundException {
    494             onCreateViewCalls.add(new Object[] {viewContext, parent, name, attrs});
    495             if ("LinearLayout".equals(name)) {
    496                 viewContext = new ContextWrapper(viewContext);
    497             }
    498             String prefix = null;
    499             int dotIndex = name.lastIndexOf('.');
    500             if (dotIndex >= 0) {
    501                 prefix = name.substring(0, dotIndex + 1);
    502                 name = name.substring(dotIndex + 1);
    503             } else {
    504                 prefix = "android.widget.";
    505             }
    506 
    507             return createView(viewContext, name, prefix, attrs);
    508         }
    509     }
    510 
    511     private XmlResourceParser getParser() {
    512         XmlResourceParser parser = null;
    513         ActivityInfo ai = null;
    514         ComponentName mComponentName = new ComponentName(mContext,
    515                 MockActivity.class);
    516         try {
    517             ai = mContext.getPackageManager().getActivityInfo(mComponentName,
    518                     PackageManager.GET_META_DATA);
    519             parser = ai.loadXmlMetaData(mContext.getPackageManager(),
    520                     "android.view.merge");
    521         } catch (Exception e) {
    522         }
    523         return parser;
    524     }
    525 }
    526