1 /* 2 * Copyright (C) 2025 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 package com.android.car.scalableui.loader.xml; 17 18 import static android.view.Display.DEFAULT_DISPLAY; 19 20 import static com.android.car.scalableui.model.Alpha.DEFAULT_ALPHA; 21 import static com.android.car.scalableui.model.Layer.DEFAULT_LAYER; 22 import static com.android.car.scalableui.model.Transition.DEFAULT_DURATION; 23 import static com.android.car.scalableui.model.Visibility.DEFAULT_VISIBILITY; 24 25 import android.animation.Animator; 26 import android.animation.AnimatorInflater; 27 import android.content.Context; 28 import android.content.res.Resources; 29 import android.graphics.Insets; 30 import android.util.AttributeSet; 31 import android.util.DisplayMetrics; 32 import android.util.Xml; 33 import android.view.animation.AnimationUtils; 34 import android.view.animation.Interpolator; 35 36 import androidx.annotation.NonNull; 37 import androidx.annotation.Nullable; 38 39 import com.android.car.scalableui.model.Alpha; 40 import com.android.car.scalableui.model.Bounds; 41 import com.android.car.scalableui.model.Corner; 42 import com.android.car.scalableui.model.KeyFrameVariant; 43 import com.android.car.scalableui.model.Layer; 44 import com.android.car.scalableui.model.PanelState; 45 import com.android.car.scalableui.model.Role; 46 import com.android.car.scalableui.model.Transition; 47 import com.android.car.scalableui.model.Variant; 48 import com.android.car.scalableui.model.Visibility; 49 50 import org.xmlpull.v1.XmlPullParser; 51 import org.xmlpull.v1.XmlPullParserException; 52 53 import java.io.IOException; 54 import java.util.ArrayList; 55 import java.util.List; 56 import java.util.Locale; 57 58 /** 59 * A utility class that uses a {@link XmlPullParser} to create a {@link PanelState} object. 60 */ 61 public class PanelStateXmlParser { 62 private static final String TAG = PanelStateXmlParser.class.getSimpleName(); 63 64 // --- Panel Tags --- 65 public static final String PANEL_TAG = "Panel"; 66 public static final String ID_ATTRIBUTE = "id"; 67 public static final String DEFAULT_VARIANT_ATTRIBUTE = "defaultVariant"; 68 public static final String ROLE_ATTRIBUTE = "role"; 69 public static final String DISPLAY_ID = "displayId"; 70 public static final String DEFAULT_LAYER_ATTRIBUTE = "defaultLayer"; 71 72 // --- Transitions Tags --- 73 public static final String TRANSITIONS_TAG = "Transitions"; 74 public static final String DEFAULT_DURATION_ATTRIBUTE = "defaultDuration"; 75 public static final String DEFAULT_INTERPOLATOR_ATTRIBUTE = "defaultInterpolator"; 76 77 // --- Transition Tags --- 78 public static final String TRANSITION_TAG = "Transition"; 79 public static final String FROM_VARIANT_ATTRIBUTE = "fromVariant"; 80 public static final String TO_VARIANT_ATTRIBUTE = "toVariant"; 81 public static final String ON_EVENT_ATTRIBUTE = "onEvent"; 82 public static final String ON_EVENT_TOKENS_ATTRIBUTE = "onEventTokens"; 83 public static final String ANIMATOR_ATTRIBUTE = "animator"; 84 public static final String DURATION_ATTRIBUTE = "duration"; 85 public static final String INTERPOLATOR_ATTRIBUTE = "interpolator"; 86 87 // --- Variant Tags --- 88 public static final String VARIANT_TAG = "Variant"; 89 public static final String PARENT_ATTRIBUTE = "parent"; 90 91 // --- KeyFrameVariant Tags --- 92 static final String KEY_FRAME_VARIANT_TAG = "KeyFrameVariant"; 93 private static final String KEY_FRAME_TAG = "KeyFrame"; 94 private static final String FRAME_ATTRIBUTE = "frame"; 95 private static final String VARIANT_ATTRIBUTE = "variant"; 96 97 // --- Visibility Tags --- 98 public static final String VISIBILITY_TAG = "Visibility"; 99 public static final String IS_VISIBLE_ATTRIBUTE = "isVisible"; 100 101 // --- Alpha Tags --- 102 public static final String ALPHA_TAG = "Alpha"; 103 public static final String ALPHA_VALUE_ATTRIBUTE = "alpha"; 104 105 // --- Layer Tags --- 106 public static final String LAYER_TAG = "Layer"; 107 public static final String LAYER_VALUE_ATTRIBUTE = "layer"; 108 109 // --- Bounds Tags --- 110 public static final String BOUNDS_TAG = "Bounds"; 111 public static final String LEFT_ATTRIBUTE = "left"; 112 public static final String RIGHT_ATTRIBUTE = "right"; 113 public static final String TOP_ATTRIBUTE = "top"; 114 public static final String BOTTOM_ATTRIBUTE = "bottom"; 115 public static final String WIDTH_ATTRIBUTE = "width"; 116 public static final String HEIGHT_ATTRIBUTE = "height"; 117 118 // --- Corner Tags --- 119 public static final String CORNER_TAG = "Corner"; 120 public static final String RADIUS_ATTRIBUTE = "radius"; 121 122 // --- Insets Tags --- 123 public static final String INSETS_TAG = "Insets"; 124 125 public static final String DIP = "dip"; 126 public static final String DP = "dp"; 127 public static final String PERCENT = "%"; 128 129 @NonNull parse(@onNull Context context, @NonNull XmlPullParser parser)130 static PanelState parse(@NonNull Context context, @NonNull XmlPullParser parser) 131 throws XmlPullParserException, IOException { 132 133 // Consume any START_DOCUMENT or whitespace events 134 int eventType = parser.getEventType(); 135 while (eventType == XmlPullParser.START_DOCUMENT 136 || (eventType == XmlPullParser.TEXT && parser.isWhitespace())) { 137 eventType = parser.next(); 138 } 139 if (eventType != XmlPullParser.START_TAG || !parser.getName().equals("Panel")) { 140 throw new XmlPullParserException("Expected <Panel> tag at the beginning but " 141 + parser.getName()); 142 } 143 144 parser.require(XmlPullParser.START_TAG, null, PANEL_TAG); 145 AttributeSet attrs = Xml.asAttributeSet(parser); 146 String id = attrs.getAttributeValue(null, ID_ATTRIBUTE); 147 String displayIdStr = attrs.getAttributeValue(null, DISPLAY_ID); 148 int displayId = (displayIdStr == null) ? DEFAULT_DISPLAY : Integer.parseInt(displayIdStr); 149 String defaultVariant = attrs.getAttributeValue(null, DEFAULT_VARIANT_ATTRIBUTE); 150 int roleValue = attrs.getAttributeResourceValue(null, ROLE_ATTRIBUTE, 0); 151 152 Integer defaultLayer = null; 153 if (attrs.getAttributeValue(null, DEFAULT_LAYER_ATTRIBUTE) != null) { 154 int resId = attrs.getAttributeResourceValue(null, DEFAULT_LAYER_ATTRIBUTE, 0); 155 if (resId != 0) { 156 defaultLayer = context.getResources().getInteger(resId); 157 } else { 158 defaultLayer = 159 attrs.getAttributeIntValue(null, DEFAULT_LAYER_ATTRIBUTE, DEFAULT_LAYER); 160 } 161 } 162 163 PanelState.Builder builder = new PanelState.Builder(id, new Role(roleValue)); 164 builder.setDisplayId(displayId); 165 builder.setDefaultVariant(defaultVariant); 166 PanelState panelState = builder.build(); 167 168 while (parser.next() != XmlPullParser.END_TAG) { 169 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 170 String name = parser.getName(); 171 switch (name) { 172 case VARIANT_TAG: 173 panelState.addVariant( 174 parseVariant(context, panelState, defaultLayer, parser)); 175 break; 176 case KEY_FRAME_VARIANT_TAG: 177 panelState.addVariant(parseKeyFrameVariant(panelState, parser)); 178 break; 179 case TRANSITIONS_TAG: 180 List<Transition> transitions = parseTransitions(context, panelState, parser); 181 for (Transition transition : transitions) { 182 panelState.addTransition(transition); 183 } 184 break; 185 default: 186 XmlPullParserHelper.skip(parser); 187 } 188 } 189 panelState.setVariant(defaultVariant); // Set the initial variant 190 return panelState; 191 } 192 193 @NonNull parseKeyFrameVariant( @onNull PanelState panelState, @NonNull XmlPullParser parser)194 private static Variant parseKeyFrameVariant( 195 @NonNull PanelState panelState, 196 @NonNull XmlPullParser parser) throws IOException, XmlPullParserException { 197 parser.require(XmlPullParser.START_TAG, null, KEY_FRAME_VARIANT_TAG); 198 AttributeSet attrs = Xml.asAttributeSet(parser); 199 String id = attrs.getAttributeValue(null, ID_ATTRIBUTE); 200 String parentStr = attrs.getAttributeValue(null, PARENT_ATTRIBUTE); 201 Variant parent = panelState.getVariant(parentStr); 202 KeyFrameVariant.Builder builder = new KeyFrameVariant.Builder(id); 203 if (parent != null) { 204 builder.setParent(parent); 205 } 206 while (parser.next() != XmlPullParser.END_TAG) { 207 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 208 String name = parser.getName(); 209 if (name.equals(KEY_FRAME_TAG)) { 210 builder.addKeyFrame(parseKeyFrame(panelState, parser)); 211 } else { 212 XmlPullParserHelper.skip(parser); 213 } 214 } 215 return builder.build(); 216 } 217 parseKeyFrame( @onNull PanelState panelState, @NonNull XmlPullParser parser)218 private static KeyFrameVariant.KeyFrame parseKeyFrame( 219 @NonNull PanelState panelState, 220 @NonNull XmlPullParser parser) throws XmlPullParserException, IOException { 221 parser.require(XmlPullParser.START_TAG, null, KEY_FRAME_TAG); 222 AttributeSet attrs = Xml.asAttributeSet(parser); 223 int frame = attrs.getAttributeIntValue(null, FRAME_ATTRIBUTE, 0); 224 String variant = attrs.getAttributeValue(null, VARIANT_ATTRIBUTE); 225 parser.nextTag(); 226 parser.require(XmlPullParser.END_TAG, null, KEY_FRAME_TAG); 227 Variant panelVariant = panelState.getVariant(variant); 228 if (panelVariant == null) { 229 throw new XmlPullParserException("Variant not found: " + variant); 230 } 231 return new KeyFrameVariant.KeyFrame.Builder(frame, panelVariant).build(); 232 } 233 234 @NonNull parseVariant( @onNull Context context, @NonNull PanelState panelState, @Nullable Integer defaultLayer, @NonNull XmlPullParser parser)235 private static Variant parseVariant( 236 @NonNull Context context, 237 @NonNull PanelState panelState, 238 @Nullable Integer defaultLayer, 239 @NonNull XmlPullParser parser) 240 throws IOException, XmlPullParserException { 241 parser.require(XmlPullParser.START_TAG, null, VARIANT_TAG); 242 AttributeSet attrs = Xml.asAttributeSet(parser); 243 244 String id = attrs.getAttributeValue(null, ID_ATTRIBUTE); 245 String parentVariantId = attrs.getAttributeValue(null, PARENT_ATTRIBUTE); 246 Variant parentVariant = panelState.getVariant(parentVariantId); 247 248 Variant.Builder variantBuilder = new Variant.Builder(id); 249 variantBuilder.setLayer(defaultLayer); 250 variantBuilder.setParent(parentVariant); 251 while (parser.next() != XmlPullParser.END_TAG) { 252 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 253 String name = parser.getName(); 254 switch (name) { 255 case VISIBILITY_TAG: 256 variantBuilder.setVisibility(parseVisibility(parser).isVisible()); 257 break; 258 case ALPHA_TAG: 259 variantBuilder.setAlpha(parseAlpha(context, parser).getAlpha()); 260 break; 261 case LAYER_TAG: 262 variantBuilder.setLayer(parseLayer(context, parser).getLayer()); 263 break; 264 case BOUNDS_TAG: 265 variantBuilder.setBounds(parseBounds(context, parser).getRect()); 266 break; 267 case CORNER_TAG: 268 variantBuilder.setCornerRadius(parseCorner(context, parser).getRadius()); 269 break; 270 case INSETS_TAG: 271 variantBuilder.setInsets(parseInsets(context, parser)); 272 break; 273 default: 274 XmlPullParserHelper.skip(parser); // Skip other nested tags 275 } 276 } 277 return variantBuilder.build(); 278 } 279 280 @NonNull parseVisibility(@onNull XmlPullParser parser)281 private static Visibility parseVisibility(@NonNull XmlPullParser parser) 282 throws IOException, XmlPullParserException { 283 parser.require(XmlPullParser.START_TAG, null, VISIBILITY_TAG); 284 AttributeSet attrs = Xml.asAttributeSet(parser); 285 boolean isVisible = 286 attrs.getAttributeBooleanValue(null, IS_VISIBLE_ATTRIBUTE, DEFAULT_VISIBILITY); 287 288 while (parser.next() != XmlPullParser.END_TAG) { 289 XmlPullParserHelper.skip(parser); // Skip any nested tags 290 } 291 292 return new Visibility.Builder().setIsVisible(isVisible).build(); 293 } 294 295 @NonNull parseAlpha(@onNull Context context, @NonNull XmlPullParser parser)296 private static Alpha parseAlpha(@NonNull Context context, @NonNull XmlPullParser parser) 297 throws IOException, XmlPullParserException { 298 parser.require(XmlPullParser.START_TAG, null, ALPHA_TAG); 299 AttributeSet attrs = Xml.asAttributeSet(parser); 300 float alpha = DEFAULT_ALPHA; 301 int resId = attrs.getAttributeResourceValue(null, ALPHA_VALUE_ATTRIBUTE, 0); 302 if (resId != 0) { 303 alpha = context.getResources().getFloat(resId); 304 } else { 305 alpha = attrs.getAttributeFloatValue(null, ALPHA_VALUE_ATTRIBUTE, DEFAULT_ALPHA); 306 } 307 308 while (parser.next() != XmlPullParser.END_TAG) { 309 XmlPullParserHelper.skip(parser); // Skip any nested tags 310 } 311 312 return new Alpha.Builder().setAlpha(alpha).build(); 313 } 314 315 @NonNull parseLayer(@onNull Context context, @NonNull XmlPullParser parser)316 private static Layer parseLayer(@NonNull Context context, @NonNull XmlPullParser parser) 317 throws IOException, XmlPullParserException { 318 parser.require(XmlPullParser.START_TAG, null, LAYER_TAG); 319 AttributeSet attrs = Xml.asAttributeSet(parser); 320 321 int layer = DEFAULT_LAYER; 322 int resId = attrs.getAttributeResourceValue(null, LAYER_VALUE_ATTRIBUTE, 0); 323 if (resId != 0) { 324 layer = context.getResources().getInteger(resId); 325 } else { 326 layer = attrs.getAttributeIntValue(null, LAYER_VALUE_ATTRIBUTE, DEFAULT_LAYER); 327 } 328 329 while (parser.next() != XmlPullParser.END_TAG) { 330 XmlPullParserHelper.skip(parser); // Skip any nested tags 331 } 332 333 return new Layer.Builder().setLayer(layer).build(); 334 } 335 336 @NonNull parseBounds(@onNull Context context, @NonNull XmlPullParser parser)337 private static Bounds parseBounds(@NonNull Context context, @NonNull XmlPullParser parser) 338 throws IOException, XmlPullParserException { 339 340 parser.require(XmlPullParser.START_TAG, null, BOUNDS_TAG); 341 AttributeSet attrs = Xml.asAttributeSet(parser); 342 343 Integer left = getDimensionPixelSize(context, attrs, LEFT_ATTRIBUTE, true); 344 Integer top = getDimensionPixelSize(context, attrs, TOP_ATTRIBUTE, false); 345 Integer right = getDimensionPixelSize(context, attrs, RIGHT_ATTRIBUTE, true); 346 Integer bottom = getDimensionPixelSize(context, attrs, BOTTOM_ATTRIBUTE, false); 347 348 Integer width = getDimensionPixelSize(context, attrs, WIDTH_ATTRIBUTE, true); 349 Integer height = getDimensionPixelSize(context, attrs, HEIGHT_ATTRIBUTE, false); 350 351 while (parser.next() != XmlPullParser.END_TAG) { 352 XmlPullParserHelper.skip(parser); // Skip any nested tags 353 } 354 355 return new Bounds.Builder() 356 .setLeft(left) 357 .setTop(top) 358 .setRight(right) 359 .setBottom(bottom) 360 .setWidth(width) 361 .setHeight(height) 362 .build(); 363 } 364 365 @NonNull parseCorner(Context context, XmlPullParser parser)366 private static Corner parseCorner(Context context, XmlPullParser parser) 367 throws IOException, XmlPullParserException { 368 parser.require(XmlPullParser.START_TAG, null, CORNER_TAG); 369 AttributeSet attrs = Xml.asAttributeSet(parser); 370 Integer radius = getDimensionPixelSize(context, attrs, RADIUS_ATTRIBUTE, false); 371 372 while (parser.next() != XmlPullParser.END_TAG) { 373 XmlPullParserHelper.skip(parser); // Skip any nested tags 374 } 375 376 return new Corner.Builder() 377 .setRadius(radius) 378 .build(); 379 } 380 parseInsets(@onNull Context context, @NonNull XmlPullParser parser)381 private static Insets parseInsets(@NonNull Context context, @NonNull XmlPullParser parser) 382 throws IOException, XmlPullParserException { 383 384 parser.require(XmlPullParser.START_TAG, null, INSETS_TAG); 385 AttributeSet attrs = Xml.asAttributeSet(parser); 386 387 Integer left = getDimensionPixelSize(context, attrs, LEFT_ATTRIBUTE, true); 388 Integer top = getDimensionPixelSize(context, attrs, TOP_ATTRIBUTE, false); 389 Integer right = getDimensionPixelSize(context, attrs, RIGHT_ATTRIBUTE, true); 390 Integer bottom = getDimensionPixelSize(context, attrs, BOTTOM_ATTRIBUTE, false); 391 392 while (parser.next() != XmlPullParser.END_TAG) { 393 XmlPullParserHelper.skip(parser); // Skip any nested tags 394 } 395 396 return Insets.of(left, top, right, bottom); 397 } 398 399 @NonNull parseTransitions( @onNull Context context, @NonNull PanelState panelState, @NonNull XmlPullParser parser)400 private static List<Transition> parseTransitions( 401 @NonNull Context context, @NonNull PanelState panelState, @NonNull XmlPullParser parser) 402 throws XmlPullParserException, IOException { 403 parser.require(XmlPullParser.START_TAG, null, TRANSITIONS_TAG); 404 AttributeSet attrs = Xml.asAttributeSet(parser); 405 // possible lossy conversion from long to int. we're assuming the default duration can be 406 // convereted to int safely. 407 int duration = attrs.getAttributeIntValue(null, DEFAULT_DURATION_ATTRIBUTE, 408 (int) DEFAULT_DURATION); 409 int interpolatorRef = 410 attrs.getAttributeResourceValue(null, DEFAULT_INTERPOLATOR_ATTRIBUTE, 0); 411 Interpolator interpolator = 412 interpolatorRef == 0 413 ? null 414 : AnimationUtils.loadInterpolator(context, interpolatorRef); 415 416 List<Transition> result = new ArrayList<>(); 417 while (parser.next() != XmlPullParser.END_TAG) { 418 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 419 420 if (parser.getName().equals(TRANSITION_TAG)) { 421 result.add( 422 parseTransition(context, panelState, duration, interpolator, parser)); 423 } else { 424 XmlPullParserHelper.skip(parser); 425 } 426 } 427 return result; 428 } 429 430 @NonNull parseTransition( @onNull Context context, @NonNull PanelState panelState, long defaultDuration, @Nullable Interpolator defaultInterpolator, @NonNull XmlPullParser parser)431 private static Transition parseTransition( 432 @NonNull Context context, 433 @NonNull PanelState panelState, 434 long defaultDuration, 435 @Nullable Interpolator defaultInterpolator, 436 @NonNull XmlPullParser parser) 437 throws IOException, XmlPullParserException { 438 parser.require(XmlPullParser.START_TAG, null, TRANSITION_TAG); 439 AttributeSet attrs = Xml.asAttributeSet(parser); 440 441 String from = attrs.getAttributeValue(null, FROM_VARIANT_ATTRIBUTE); 442 String to = attrs.getAttributeValue(null, TO_VARIANT_ATTRIBUTE); 443 String onEvent = attrs.getAttributeValue(null, ON_EVENT_ATTRIBUTE); 444 String onEventTokens = attrs.getAttributeValue(null, ON_EVENT_TOKENS_ATTRIBUTE); 445 int animatorId = attrs.getAttributeResourceValue(null, ANIMATOR_ATTRIBUTE, 0); 446 Animator animator = 447 animatorId == 0 ? null : AnimatorInflater.loadAnimator(context, animatorId); 448 int duration = attrs.getAttributeIntValue(null, DURATION_ATTRIBUTE, (int) defaultDuration); 449 int interpolatorRef = attrs.getAttributeResourceValue(null, INTERPOLATOR_ATTRIBUTE, 0); 450 Interpolator interpolator = 451 interpolatorRef == 0 452 ? defaultInterpolator 453 : AnimationUtils.loadInterpolator(context, interpolatorRef); 454 Variant fromVariant = panelState.getVariant(from); 455 Variant toVariant = panelState.getVariant(to); 456 457 while (parser.next() != XmlPullParser.END_TAG) { 458 XmlPullParserHelper.skip(parser); // Should be no nested tags. 459 } 460 461 return new Transition.Builder(fromVariant, toVariant) 462 .setOnEvent(onEvent, onEventTokens) 463 .setAnimator(animator) 464 .setDefaultDuration(duration) 465 .setDefaultInterpolator(interpolator) 466 .build(); 467 } 468 469 /** 470 * Helper method to get a dimension pixel size from an attribute set. 471 * 472 * @param context The application context. 473 * @param attrs The attribute set. 474 * @param name The name of the attribute. 475 * @param isHorizontal Whether the dimension is horizontal (width) or vertical (height). 476 * @return The dimension pixel size. 477 */ 478 @Nullable getDimensionPixelSize(@onNull Context context, @NonNull AttributeSet attrs, @NonNull String name, boolean isHorizontal)479 private static Integer getDimensionPixelSize(@NonNull Context context, 480 @NonNull AttributeSet attrs, @NonNull String name, boolean isHorizontal) { 481 int resId = attrs.getAttributeResourceValue(null, name, 0); 482 if (resId != 0) { 483 return context.getResources().getDimensionPixelSize(resId); 484 } 485 String dimenStr = attrs.getAttributeValue(null, name); 486 if (dimenStr == null) { 487 return null; 488 } 489 if (dimenStr.toLowerCase(Locale.ROOT).endsWith(DP)) { 490 String valueStr = dimenStr.substring(0, dimenStr.length() - DP.length()); 491 float value = Float.parseFloat(valueStr); 492 return (int) (value * Resources.getSystem().getDisplayMetrics().density); 493 } else if (dimenStr.toLowerCase(Locale.ROOT).endsWith(DIP)) { 494 String valueStr = dimenStr.substring(0, dimenStr.length() - DIP.length()); 495 float value = Float.parseFloat(valueStr); 496 return (int) (value * Resources.getSystem().getDisplayMetrics().density); 497 } else if (dimenStr.toLowerCase(Locale.ROOT).endsWith(PERCENT)) { 498 String valueStr = dimenStr.substring(0, dimenStr.length() - PERCENT.length()); 499 float value = Float.parseFloat(valueStr); 500 DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics(); 501 if (isHorizontal) { 502 return (int) (value * displayMetrics.widthPixels / 100); 503 } else { 504 return (int) (value * displayMetrics.heightPixels / 100); 505 } 506 } else { 507 // The default value is never returned because `attrs.getAttributeValue` is not null. 508 return attrs.getAttributeIntValue(null, name, 0); 509 } 510 } 511 } 512