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