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.AdapterBinding; 20 import com.android.ide.common.rendering.api.AndroidConstants; 21 import com.android.ide.common.rendering.api.ILayoutLog; 22 import com.android.ide.common.rendering.api.LayoutlibCallback; 23 import com.android.ide.common.rendering.api.MergeCookie; 24 import com.android.ide.common.rendering.api.ResourceNamespace; 25 import com.android.ide.common.rendering.api.ResourceReference; 26 import com.android.ide.common.rendering.api.ResourceValue; 27 import com.android.layoutlib.bridge.Bridge; 28 import com.android.layoutlib.bridge.BridgeConstants; 29 import com.android.layoutlib.bridge.MockView; 30 import com.android.layoutlib.bridge.android.BridgeContext; 31 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 32 import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil; 33 import com.android.layoutlib.bridge.android.support.RecyclerViewUtil; 34 import com.android.layoutlib.bridge.impl.ParserFactory; 35 import com.android.layoutlib.bridge.impl.binding.FakeAdapter; 36 import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; 37 import com.android.layoutlib.common.util.ReflectionUtils; 38 import com.android.tools.layoutlib.annotations.NotNull; 39 import com.android.tools.layoutlib.annotations.Nullable; 40 41 import org.xmlpull.v1.XmlPullParser; 42 43 import android.annotation.NonNull; 44 import android.content.Context; 45 import android.content.res.TypedArray; 46 import android.graphics.drawable.Animatable; 47 import android.graphics.drawable.Drawable; 48 import android.util.AttributeSet; 49 import android.util.Pair; 50 import android.util.ResolvingAttributeSet; 51 import android.view.View.OnAttachStateChangeListener; 52 import android.widget.AbsListView; 53 import android.widget.AbsSpinner; 54 import android.widget.AdapterView; 55 import android.widget.ExpandableListView; 56 import android.widget.ImageView; 57 import android.widget.ListView; 58 import android.widget.NumberPicker; 59 60 import java.lang.reflect.Constructor; 61 import java.lang.reflect.InvocationTargetException; 62 import java.lang.reflect.Method; 63 import java.util.HashMap; 64 import java.util.Map; 65 import java.util.function.BiFunction; 66 67 import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext; 68 69 /** 70 * Custom implementation of {@link LayoutInflater} to handle custom views. 71 */ 72 public final class BridgeInflater extends LayoutInflater { 73 private static final String INFLATER_CLASS_ATTR_NAME = "viewInflaterClass"; 74 private static final ResourceReference RES_AUTO_INFLATER_CLASS_ATTR = 75 ResourceReference.attr(ResourceNamespace.RES_AUTO, INFLATER_CLASS_ATTR_NAME); 76 private static final ResourceReference LEGACY_APPCOMPAT_INFLATER_CLASS_ATTR = 77 ResourceReference.attr(ResourceNamespace.APPCOMPAT_LEGACY, INFLATER_CLASS_ATTR_NAME); 78 private static final ResourceReference ANDROIDX_APPCOMPAT_INFLATER_CLASS_ATTR = 79 ResourceReference.attr(ResourceNamespace.APPCOMPAT, INFLATER_CLASS_ATTR_NAME); 80 private static final String LEGACY_DEFAULT_APPCOMPAT_INFLATER_NAME = 81 "android.support.v7.app.AppCompatViewInflater"; 82 private static final String ANDROIDX_DEFAULT_APPCOMPAT_INFLATER_NAME = 83 "androidx.appcompat.app.AppCompatViewInflater"; 84 private final LayoutlibCallback mLayoutlibCallback; 85 86 private boolean mIsInMerge = false; 87 private ResourceReference mResourceReference; 88 private Map<View, String> mOpenDrawerLayouts; 89 90 // Keep in sync with the same value in LayoutInflater. 91 private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme }; 92 93 /** 94 * List of class prefixes which are tried first by default. 95 * <p/> 96 * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater. 97 */ 98 private static final String[] sClassPrefixList = { 99 "android.widget.", 100 "android.webkit.", 101 "android.app." 102 }; 103 private BiFunction<String, AttributeSet, View> mCustomInflater; 104 getClassPrefixList()105 public static String[] getClassPrefixList() { 106 return sClassPrefixList; 107 } 108 BridgeInflater(LayoutInflater original, Context newContext)109 private BridgeInflater(LayoutInflater original, Context newContext) { 110 super(original, newContext); 111 newContext = getBaseContext(newContext); 112 mLayoutlibCallback = (newContext instanceof BridgeContext) ? 113 ((BridgeContext) newContext).getLayoutlibCallback() : 114 null; 115 } 116 117 /** 118 * Instantiate a new BridgeInflater with an {@link LayoutlibCallback} object. 119 * 120 * @param context The Android application context. 121 * @param layoutlibCallback the {@link LayoutlibCallback} object. 122 */ BridgeInflater(BridgeContext context, LayoutlibCallback layoutlibCallback)123 public BridgeInflater(BridgeContext context, LayoutlibCallback layoutlibCallback) { 124 super(context); 125 mLayoutlibCallback = layoutlibCallback; 126 mConstructorArgs[0] = context; 127 } 128 129 @Override onCreateView(String name, AttributeSet attrs)130 public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { 131 View view = createViewFromCustomInflater(name, attrs); 132 133 if (view == null) { 134 try { 135 // First try to find a class using the default Android prefixes 136 for (String prefix : sClassPrefixList) { 137 try { 138 view = createView(name, prefix, attrs); 139 if (view != null) { 140 break; 141 } 142 } catch (ClassNotFoundException e) { 143 // Ignore. We'll try again using the base class below. 144 } 145 } 146 147 // Next try using the parent loader. This will most likely only work for 148 // fully-qualified class names. 149 try { 150 if (view == null) { 151 view = super.onCreateView(name, attrs); 152 } 153 } catch (ClassNotFoundException e) { 154 // Ignore. We'll try again using the custom view loader below. 155 } 156 157 // Finally try again using the custom view loader 158 if (view == null) { 159 view = loadCustomView(name, attrs); 160 } 161 } catch (InflateException e) { 162 // Don't catch the InflateException below as that results in hiding the real cause. 163 throw e; 164 } catch (Exception e) { 165 // Wrap the real exception in a ClassNotFoundException, so that the calling method 166 // can deal with it. 167 throw new ClassNotFoundException("onCreateView", e); 168 } 169 } 170 171 setupViewInContext(view, attrs); 172 173 return view; 174 } 175 176 /** 177 * Finds the createView method in the given customInflaterClass. Since createView is 178 * currently package protected, it will show in the declared class so we iterate up the 179 * hierarchy and return the first instance we find. 180 * The returned method will be accessible. 181 */ 182 @NotNull getCreateViewMethod(Class<?> customInflaterClass)183 private static Method getCreateViewMethod(Class<?> customInflaterClass) throws NoSuchMethodException { 184 Class<?> current = customInflaterClass; 185 do { 186 try { 187 Method method = current.getDeclaredMethod("createView", View.class, String.class, 188 Context.class, AttributeSet.class, boolean.class, boolean.class, 189 boolean.class, boolean.class); 190 method.setAccessible(true); 191 return method; 192 } catch (NoSuchMethodException ignore) { 193 } 194 current = current.getSuperclass(); 195 } while (current != null && current != Object.class); 196 197 throw new NoSuchMethodException(); 198 } 199 200 /** 201 * Finds the custom inflater class. If it's defined in the theme, we'll use that one (if the 202 * class does not exist, null is returned). 203 * If {@code viewInflaterClass} is not defined in the theme, we'll try to instantiate 204 * {@code android.support.v7.app.AppCompatViewInflater} 205 */ 206 @Nullable findCustomInflater(@otNull BridgeContext bc, @NotNull LayoutlibCallback layoutlibCallback)207 private static Class<?> findCustomInflater(@NotNull BridgeContext bc, 208 @NotNull LayoutlibCallback layoutlibCallback) { 209 ResourceReference attrRef; 210 if (layoutlibCallback.isResourceNamespacingRequired()) { 211 if (layoutlibCallback.hasLegacyAppCompat()) { 212 attrRef = LEGACY_APPCOMPAT_INFLATER_CLASS_ATTR; 213 } else if (layoutlibCallback.hasAndroidXAppCompat()) { 214 attrRef = ANDROIDX_APPCOMPAT_INFLATER_CLASS_ATTR; 215 } else { 216 return null; 217 } 218 } else { 219 attrRef = RES_AUTO_INFLATER_CLASS_ATTR; 220 } 221 ResourceValue value = bc.getRenderResources().findItemInTheme(attrRef); 222 String inflaterName = value != null ? value.getValue() : null; 223 224 if (inflaterName != null) { 225 try { 226 return layoutlibCallback.findClass(inflaterName); 227 } catch (ClassNotFoundException ignore) { 228 } 229 230 // viewInflaterClass was defined but we couldn't find the class. 231 } else if (bc.isAppCompatTheme()) { 232 // Older versions of AppCompat do not define the viewInflaterClass so try to get it 233 // manually. 234 try { 235 if (layoutlibCallback.hasLegacyAppCompat()) { 236 return layoutlibCallback.findClass(LEGACY_DEFAULT_APPCOMPAT_INFLATER_NAME); 237 } else if (layoutlibCallback.hasAndroidXAppCompat()) { 238 return layoutlibCallback.findClass(ANDROIDX_DEFAULT_APPCOMPAT_INFLATER_NAME); 239 } 240 } catch (ClassNotFoundException ignore) { 241 } 242 } 243 244 return null; 245 } 246 247 /** 248 * Checks if there is a custom inflater and, when present, tries to instantiate the view 249 * using it. 250 */ 251 @Nullable createViewFromCustomInflater(@otNull String name, @NotNull AttributeSet attrs)252 private View createViewFromCustomInflater(@NotNull String name, @NotNull AttributeSet attrs) { 253 if (!mLayoutlibCallback.shouldUseCustomInflater()) { 254 return null; 255 } 256 if (mCustomInflater == null) { 257 Context context = getContext(); 258 context = getBaseContext(context); 259 if (context instanceof BridgeContext) { 260 BridgeContext bc = (BridgeContext) context; 261 Class<?> inflaterClass = findCustomInflater(bc, mLayoutlibCallback); 262 263 if (inflaterClass != null) { 264 try { 265 Constructor<?> constructor = inflaterClass.getDeclaredConstructor(); 266 constructor.setAccessible(true); 267 Object inflater = constructor.newInstance(); 268 Method method = getCreateViewMethod(inflaterClass); 269 mCustomInflater = (viewName, attributeSet) -> { 270 try { 271 return (View) method.invoke(inflater, null, viewName, 272 mConstructorArgs[0], 273 attributeSet, 274 false, 275 false /*readAndroidTheme*/, // No need after L 276 true /*readAppTheme*/, 277 true /*wrapContext*/); 278 } catch (IllegalAccessException | InvocationTargetException e) { 279 Bridge.getLog().error(ILayoutLog.TAG_BROKEN, e.getMessage(), e, 280 null, null); 281 } 282 return null; 283 }; 284 } catch (InvocationTargetException | IllegalAccessException | 285 NoSuchMethodException | InstantiationException ignore) { 286 } 287 } 288 } 289 290 if (mCustomInflater == null) { 291 // There is no custom inflater. We'll create a nop custom inflater to avoid the 292 // penalty of trying to instantiate again 293 mCustomInflater = (s, attributeSet) -> null; 294 } 295 } 296 297 return mCustomInflater.apply(name, attrs); 298 } 299 300 @Override createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr)301 public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, 302 boolean ignoreThemeAttr) { 303 View view = null; 304 if (name.equals("view")) { 305 // This is usually done by the superclass but this allows us catching the error and 306 // reporting something useful. 307 name = attrs.getAttributeValue(null, "class"); 308 309 if (name == null) { 310 Bridge.getLog().error(ILayoutLog.TAG_BROKEN, "Unable to inflate view tag without " + 311 "class attribute", null, null); 312 // We weren't able to resolve the view so we just pass a mock View to be able to 313 // continue rendering. 314 view = new MockView(context, attrs); 315 ((MockView) view).setText("view"); 316 } 317 } 318 319 try { 320 if (view == null) { 321 view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr); 322 } 323 } catch (InflateException e) { 324 // Creation of ContextThemeWrapper code is same as in the super method. 325 // Apply a theme wrapper, if allowed and one is specified. 326 if (!ignoreThemeAttr) { 327 final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); 328 final int themeResId = ta.getResourceId(0, 0); 329 if (themeResId != 0) { 330 context = new ContextThemeWrapper(context, themeResId); 331 } 332 ta.recycle(); 333 } 334 if (!(e.getCause() instanceof ClassNotFoundException)) { 335 // There is some unknown inflation exception in inflating a View that was found. 336 view = new MockView(context, attrs); 337 ((MockView) view).setText(name); 338 Bridge.getLog().error(ILayoutLog.TAG_BROKEN, e.getMessage(), e, null, null); 339 } else { 340 final Object lastContext = mConstructorArgs[0]; 341 mConstructorArgs[0] = context; 342 // try to load the class from using the custom view loader 343 try { 344 view = loadCustomView(name, attrs); 345 } catch (Exception e2) { 346 // Wrap the real exception in an InflateException so that the calling 347 // method can deal with it. 348 InflateException exception = new InflateException(); 349 if (!e2.getClass().equals(ClassNotFoundException.class)) { 350 exception.initCause(e2); 351 } else { 352 exception.initCause(e); 353 } 354 throw exception; 355 } finally { 356 mConstructorArgs[0] = lastContext; 357 } 358 } 359 } 360 361 setupViewInContext(view, attrs); 362 363 return view; 364 } 365 366 @Override inflate(int resource, ViewGroup root)367 public View inflate(int resource, ViewGroup root) { 368 Context context = getContext(); 369 context = getBaseContext(context); 370 if (context instanceof BridgeContext) { 371 BridgeContext bridgeContext = (BridgeContext)context; 372 373 ResourceValue value = null; 374 375 ResourceReference layoutInfo = Bridge.resolveResourceId(resource); 376 if (layoutInfo == null) { 377 layoutInfo = mLayoutlibCallback.resolveResourceId(resource); 378 379 } 380 if (layoutInfo != null) { 381 value = bridgeContext.getRenderResources().getResolvedResource(layoutInfo); 382 } 383 384 if (value != null) { 385 String path = value.getValue(); 386 try { 387 XmlPullParser parser = ParserFactory.create(path, true); 388 if (parser == null) { 389 return null; 390 } 391 392 BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( 393 parser, bridgeContext, value.getNamespace()); 394 395 return inflate(bridgeParser, root); 396 } catch (Exception e) { 397 Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_READ, 398 "Failed to parse file " + path, e, null, null); 399 400 return null; 401 } 402 } 403 } 404 return null; 405 } 406 407 /** 408 * Instantiates the given view name and returns the instance. If the view doesn't exist, a 409 * MockView or null might be returned. 410 * @param name the custom view name 411 * @param attrs the {@link AttributeSet} to be passed to the view constructor 412 * @param silent if true, errors while loading the view won't be reported and, if the view 413 * doesn't exist, null will be returned. 414 */ loadCustomView(String name, AttributeSet attrs, boolean silent)415 private View loadCustomView(String name, AttributeSet attrs, boolean silent) throws Exception { 416 if (mLayoutlibCallback != null) { 417 // first get the classname in case it's not the node name 418 if (name.equals("view")) { 419 name = attrs.getAttributeValue(null, "class"); 420 if (name == null) { 421 return null; 422 } 423 } 424 425 mConstructorArgs[1] = attrs; 426 427 Object customView = silent ? 428 mLayoutlibCallback.loadClass(name, mConstructorSignature, mConstructorArgs) 429 : mLayoutlibCallback.loadView(name, mConstructorSignature, mConstructorArgs); 430 431 if (customView instanceof View) { 432 return (View)customView; 433 } 434 } 435 436 return null; 437 } 438 loadCustomView(String name, AttributeSet attrs)439 private View loadCustomView(String name, AttributeSet attrs) throws Exception { 440 return loadCustomView(name, attrs, false); 441 } 442 setupViewInContext(View view, AttributeSet attrs)443 private void setupViewInContext(View view, AttributeSet attrs) { 444 Context context = getContext(); 445 context = getBaseContext(context); 446 if (!(context instanceof BridgeContext)) { 447 return; 448 } 449 450 BridgeContext bc = (BridgeContext) context; 451 // get the view key 452 Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge); 453 if (viewKey != null) { 454 bc.addViewKey(view, viewKey); 455 } 456 String scrollPosX = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollX"); 457 if (scrollPosX != null && scrollPosX.endsWith("px")) { 458 int value = Integer.parseInt(scrollPosX.substring(0, scrollPosX.length() - 2)); 459 bc.setScrollXPos(view, value); 460 } 461 String scrollPosY = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY"); 462 if (scrollPosY != null && scrollPosY.endsWith("px")) { 463 int value = Integer.parseInt(scrollPosY.substring(0, scrollPosY.length() - 2)); 464 bc.setScrollYPos(view, value); 465 } 466 if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) { 467 int resourceId = 0; 468 int attrItemCountValue = attrs.getAttributeIntValue(BridgeConstants.NS_TOOLS_URI, 469 BridgeConstants.ATTR_ITEM_COUNT, -1); 470 if (attrs instanceof ResolvingAttributeSet) { 471 ResourceValue attrListItemValue = 472 ((ResolvingAttributeSet) attrs).getResolvedAttributeValue( 473 BridgeConstants.NS_TOOLS_URI, BridgeConstants.ATTR_LIST_ITEM); 474 if (attrListItemValue != null) { 475 resourceId = bc.getResourceId(attrListItemValue.asReference(), 0); 476 } 477 } 478 RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId, attrItemCountValue); 479 } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) { 480 String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, 481 BridgeConstants.ATTR_OPEN_DRAWER); 482 if (attrVal != null) { 483 getDrawerLayoutMap().put(view, attrVal); 484 } 485 } 486 else if (view instanceof NumberPicker) { 487 NumberPicker numberPicker = (NumberPicker) view; 488 String minValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "minValue"); 489 if (minValue != null) { 490 numberPicker.setMinValue(Integer.parseInt(minValue)); 491 } 492 String maxValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "maxValue"); 493 if (maxValue != null) { 494 numberPicker.setMaxValue(Integer.parseInt(maxValue)); 495 } 496 } 497 else if (view instanceof ImageView) { 498 ImageView img = (ImageView) view; 499 Drawable drawable = img.getDrawable(); 500 if (drawable instanceof Animatable) { 501 if (!((Animatable) drawable).isRunning()) { 502 ((Animatable) drawable).start(); 503 } 504 } 505 } 506 else if (view instanceof ViewStub) { 507 // By default, ViewStub will be set to GONE and won't be inflate. If the XML has the 508 // tools:visibility attribute we'll workaround that behavior. 509 String visibility = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, 510 AndroidConstants.ATTR_VISIBILITY); 511 512 boolean isVisible = "visible".equals(visibility); 513 if (isVisible || "invisible".equals(visibility)) { 514 // We can not inflate the view until is attached to its parent so we need to delay 515 // the setVisible call until after that happens. 516 final int visibilityValue = isVisible ? View.VISIBLE : View.INVISIBLE; 517 view.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { 518 @Override 519 public void onViewAttachedToWindow(View v) { 520 v.removeOnAttachStateChangeListener(this); 521 view.setVisibility(visibilityValue); 522 } 523 524 @Override 525 public void onViewDetachedFromWindow(View v) {} 526 }); 527 } 528 } 529 else if (view instanceof AdapterView<?>) { 530 // We do not need data binding support for Glance ListView, the assigned adapter should 531 // handle everything itself. 532 if (isGlanceView(view)) { 533 return; 534 } 535 536 int id = view.getId(); 537 ResourceReference listRef = bc.resolveId(id); 538 539 if (listRef != null) { 540 Map<String, String> bindingAttributes = new HashMap<>(4); 541 String header = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, 542 BridgeConstants.ATTR_LIST_HEADER); 543 String footer = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, 544 BridgeConstants.ATTR_LIST_FOOTER); 545 String layout = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, 546 BridgeConstants.ATTR_LIST_ITEM); 547 String columns = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, 548 BridgeConstants.ATTR_NUM_COLUMNS); 549 if (header != null) { 550 bindingAttributes.put(BridgeConstants.ATTR_LIST_HEADER, header); 551 } 552 if (footer != null) { 553 bindingAttributes.put(BridgeConstants.ATTR_LIST_FOOTER, footer); 554 } 555 if (layout != null) { 556 bindingAttributes.put(BridgeConstants.ATTR_LIST_ITEM, layout); 557 } 558 if (columns != null) { 559 bindingAttributes.put(BridgeConstants.ATTR_NUM_COLUMNS, columns); 560 } 561 562 AdapterBinding binding = 563 mLayoutlibCallback.getAdapterBinding(view, bindingAttributes); 564 if (binding != null) { 565 setAdapterBinding(view, bc, listRef, binding); 566 } 567 } 568 } 569 } 570 setAdapterBinding(View view, BridgeContext bc, ResourceReference listRef, AdapterBinding binding)571 private void setAdapterBinding(View view, BridgeContext bc, ResourceReference listRef, 572 AdapterBinding binding) { 573 if (view instanceof AbsListView) { 574 if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) && 575 view instanceof ListView) { 576 ListView list = (ListView) view; 577 578 boolean skipCallbackParser = false; 579 580 int count = binding.getHeaderCount(); 581 for (int i = 0; i < count; i++) { 582 Pair<View, Boolean> pair = 583 bc.inflateView(binding.getHeaderAt(i), list, false, 584 skipCallbackParser); 585 if (pair.first != null) { 586 list.addHeaderView(pair.first); 587 } 588 589 skipCallbackParser |= pair.second; 590 } 591 592 count = binding.getFooterCount(); 593 for (int i = 0; i < count; i++) { 594 Pair<View, Boolean> pair = 595 bc.inflateView(binding.getFooterAt(i), list, false, 596 skipCallbackParser); 597 if (pair.first != null) { 598 list.addFooterView(pair.first); 599 } 600 601 skipCallbackParser |= pair.second; 602 } 603 } 604 605 if (view instanceof ExpandableListView) { 606 ((ExpandableListView) view).setAdapter( 607 new FakeExpandableAdapter(listRef, binding, mLayoutlibCallback)); 608 } else { 609 ((AbsListView) view).setAdapter( 610 new FakeAdapter(listRef, binding, mLayoutlibCallback)); 611 } 612 } else if (view instanceof AbsSpinner) { 613 ((AbsSpinner) view).setAdapter( 614 new FakeAdapter(listRef, binding, mLayoutlibCallback)); 615 } 616 } 617 isGlanceAdapter(Class<?> clazz)618 private static boolean isGlanceAdapter(Class<?> clazz) { 619 return clazz 620 .getName() 621 .equals("androidx.glance.appwidget.preview.GlanceAppWidgetViewAdapter"); 622 } 623 624 /** 625 * Return true if the View belongs to the Glance generated hierarchy (when one of the view's 626 * parents is GlanceAppWidgetViewAdapter). 627 */ isGlanceView(View view)628 private static boolean isGlanceView(View view) { 629 if (isGlanceAdapter(view.getClass())) { 630 return true; 631 } 632 ViewParent parent = view.getParent(); 633 while (parent != null) { 634 if (isGlanceAdapter(parent.getClass())) { 635 return true; 636 } 637 parent = parent.getParent(); 638 } 639 return false; 640 } 641 setIsInMerge(boolean isInMerge)642 public void setIsInMerge(boolean isInMerge) { 643 mIsInMerge = isInMerge; 644 } 645 setResourceReference(ResourceReference reference)646 public void setResourceReference(ResourceReference reference) { 647 mResourceReference = reference; 648 } 649 650 @Override cloneInContext(Context newContext)651 public LayoutInflater cloneInContext(Context newContext) { 652 return new BridgeInflater(this, newContext); 653 } 654 getViewKeyFromParser(AttributeSet attrs, BridgeContext bc, ResourceReference resourceReference, boolean isInMerge)655 /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc, 656 ResourceReference resourceReference, boolean isInMerge) { 657 658 if (!(attrs instanceof BridgeXmlBlockParser)) { 659 return null; 660 } 661 BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs); 662 663 // get the view key 664 Object viewKey = parser.getViewCookie(); 665 666 if (viewKey == null) { 667 int currentDepth = parser.getDepth(); 668 669 // test whether we are in an included file or in a adapter binding view. 670 BridgeXmlBlockParser previousParser = bc.getPreviousParser(); 671 if (previousParser != null) { 672 // looks like we are inside an embedded layout. 673 // only apply the cookie of the calling node (<include>) if we are at the 674 // top level of the embedded layout. If there is a merge tag, then 675 // skip it and look for the 2nd level 676 int testDepth = isInMerge ? 2 : 1; 677 if (currentDepth == testDepth) { 678 viewKey = previousParser.getViewCookie(); 679 // if we are in a merge, wrap the cookie in a MergeCookie. 680 if (viewKey != null && isInMerge) { 681 viewKey = new MergeCookie(viewKey); 682 } 683 } 684 } else if (resourceReference != null && currentDepth == 1) { 685 // else if there's a resource reference, this means we are in an adapter 686 // binding case. Set the resource ref as the view cookie only for the top 687 // level view. 688 viewKey = resourceReference; 689 } 690 } 691 692 return viewKey; 693 } 694 postInflateProcess(View view)695 public void postInflateProcess(View view) { 696 if (mOpenDrawerLayouts != null) { 697 String gravity = mOpenDrawerLayouts.get(view); 698 if (gravity != null) { 699 DrawerLayoutUtil.openDrawer(view, gravity); 700 } 701 mOpenDrawerLayouts.remove(view); 702 } 703 } 704 705 @NonNull getDrawerLayoutMap()706 private Map<View, String> getDrawerLayoutMap() { 707 if (mOpenDrawerLayouts == null) { 708 mOpenDrawerLayouts = new HashMap<>(4); 709 } 710 return mOpenDrawerLayouts; 711 } 712 onDoneInflation()713 public void onDoneInflation() { 714 if (mOpenDrawerLayouts != null) { 715 mOpenDrawerLayouts.clear(); 716 } 717 } 718 } 719