RenderNode.cpp revision 5e00c7ce063116c11315639f0035aca8ad73e8cc
1/* 2 * Copyright (C) 2014 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#include "RenderNode.h" 18 19#include "BakedOpRenderer.h" 20#include "DamageAccumulator.h" 21#include "Debug.h" 22#include "LayerRenderer.h" 23#include "OpDumper.h" 24#include "RecordedOp.h" 25#include "TreeInfo.h" 26#include "utils/MathUtils.h" 27#include "utils/TraceUtils.h" 28#include "VectorDrawable.h" 29#include "renderstate/RenderState.h" 30#include "renderthread/CanvasContext.h" 31 32#include "protos/hwui.pb.h" 33#include "protos/ProtoHelpers.h" 34 35#include <algorithm> 36#include <sstream> 37#include <string> 38 39namespace android { 40namespace uirenderer { 41 42RenderNode::RenderNode() 43 : mDirtyPropertyFields(0) 44 , mNeedsDisplayListSync(false) 45 , mDisplayList(nullptr) 46 , mStagingDisplayList(nullptr) 47 , mAnimatorManager(*this) 48 , mParentCount(0) { 49} 50 51RenderNode::~RenderNode() { 52 deleteDisplayList(nullptr); 53 delete mStagingDisplayList; 54 LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!"); 55} 56 57void RenderNode::setStagingDisplayList(DisplayList* displayList, TreeObserver* observer) { 58 mNeedsDisplayListSync = true; 59 delete mStagingDisplayList; 60 mStagingDisplayList = displayList; 61 // If mParentCount == 0 we are the sole reference to this RenderNode, 62 // so immediately free the old display list 63 if (!mParentCount && !mStagingDisplayList) { 64 deleteDisplayList(observer); 65 } 66} 67 68/** 69 * This function is a simplified version of replay(), where we simply retrieve and log the 70 * display list. This function should remain in sync with the replay() function. 71 */ 72void RenderNode::output(uint32_t level, const char* label) { 73 ALOGD("%s (%s %p%s%s%s%s%s)", 74 label, 75 getName(), 76 this, 77 (MathUtils::isZero(properties().getAlpha()) ? ", zero alpha" : ""), 78 (properties().hasShadow() ? ", casting shadow" : ""), 79 (isRenderable() ? "" : ", empty"), 80 (properties().getProjectBackwards() ? ", projected" : ""), 81 (mLayer != nullptr ? ", on HW Layer" : "")); 82 properties().debugOutputProperties(level + 1); 83 84 if (mDisplayList) { 85 for (auto&& op : mDisplayList->getOps()) { 86 std::stringstream strout; 87 OpDumper::dump(*op, strout, level + 1); 88 if (op->opId == RecordedOpId::RenderNodeOp) { 89 auto rnOp = reinterpret_cast<const RenderNodeOp*>(op); 90 rnOp->renderNode->output(level + 1, strout.str().c_str()); 91 } else { 92 ALOGD("%s", strout.str().c_str()); 93 } 94 } 95 } 96 ALOGD("%*s/RenderNode(%s %p)", level * 2, "", getName(), this); 97} 98 99void RenderNode::copyTo(proto::RenderNode *pnode) { 100 pnode->set_id(static_cast<uint64_t>( 101 reinterpret_cast<uintptr_t>(this))); 102 pnode->set_name(mName.string(), mName.length()); 103 104 proto::RenderProperties* pprops = pnode->mutable_properties(); 105 pprops->set_left(properties().getLeft()); 106 pprops->set_top(properties().getTop()); 107 pprops->set_right(properties().getRight()); 108 pprops->set_bottom(properties().getBottom()); 109 pprops->set_clip_flags(properties().getClippingFlags()); 110 pprops->set_alpha(properties().getAlpha()); 111 pprops->set_translation_x(properties().getTranslationX()); 112 pprops->set_translation_y(properties().getTranslationY()); 113 pprops->set_translation_z(properties().getTranslationZ()); 114 pprops->set_elevation(properties().getElevation()); 115 pprops->set_rotation(properties().getRotation()); 116 pprops->set_rotation_x(properties().getRotationX()); 117 pprops->set_rotation_y(properties().getRotationY()); 118 pprops->set_scale_x(properties().getScaleX()); 119 pprops->set_scale_y(properties().getScaleY()); 120 pprops->set_pivot_x(properties().getPivotX()); 121 pprops->set_pivot_y(properties().getPivotY()); 122 pprops->set_has_overlapping_rendering(properties().getHasOverlappingRendering()); 123 pprops->set_pivot_explicitly_set(properties().isPivotExplicitlySet()); 124 pprops->set_project_backwards(properties().getProjectBackwards()); 125 pprops->set_projection_receiver(properties().isProjectionReceiver()); 126 set(pprops->mutable_clip_bounds(), properties().getClipBounds()); 127 128 const Outline& outline = properties().getOutline(); 129 if (outline.getType() != Outline::Type::None) { 130 proto::Outline* poutline = pprops->mutable_outline(); 131 poutline->clear_path(); 132 if (outline.getType() == Outline::Type::Empty) { 133 poutline->set_type(proto::Outline_Type_Empty); 134 } else if (outline.getType() == Outline::Type::ConvexPath) { 135 poutline->set_type(proto::Outline_Type_ConvexPath); 136 if (const SkPath* path = outline.getPath()) { 137 set(poutline->mutable_path(), *path); 138 } 139 } else if (outline.getType() == Outline::Type::RoundRect) { 140 poutline->set_type(proto::Outline_Type_RoundRect); 141 } else { 142 ALOGW("Uknown outline type! %d", static_cast<int>(outline.getType())); 143 poutline->set_type(proto::Outline_Type_None); 144 } 145 poutline->set_should_clip(outline.getShouldClip()); 146 poutline->set_alpha(outline.getAlpha()); 147 poutline->set_radius(outline.getRadius()); 148 set(poutline->mutable_bounds(), outline.getBounds()); 149 } else { 150 pprops->clear_outline(); 151 } 152 153 const RevealClip& revealClip = properties().getRevealClip(); 154 if (revealClip.willClip()) { 155 proto::RevealClip* prevealClip = pprops->mutable_reveal_clip(); 156 prevealClip->set_x(revealClip.getX()); 157 prevealClip->set_y(revealClip.getY()); 158 prevealClip->set_radius(revealClip.getRadius()); 159 } else { 160 pprops->clear_reveal_clip(); 161 } 162 163 pnode->clear_children(); 164 if (mDisplayList) { 165 for (auto&& child : mDisplayList->getChildren()) { 166 child->renderNode->copyTo(pnode->add_children()); 167 } 168 } 169} 170 171int RenderNode::getDebugSize() { 172 int size = sizeof(RenderNode); 173 if (mStagingDisplayList) { 174 size += mStagingDisplayList->getUsedSize(); 175 } 176 if (mDisplayList && mDisplayList != mStagingDisplayList) { 177 size += mDisplayList->getUsedSize(); 178 } 179 return size; 180} 181 182void RenderNode::prepareTree(TreeInfo& info) { 183 ATRACE_CALL(); 184 LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing"); 185 186 // Functors don't correctly handle stencil usage of overdraw debugging - shove 'em in a layer. 187 bool functorsNeedLayer = Properties::debugOverdraw; 188 189 prepareTreeImpl(info, functorsNeedLayer); 190} 191 192void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) { 193 mAnimatorManager.addAnimator(animator); 194} 195 196void RenderNode::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) { 197 mAnimatorManager.removeAnimator(animator); 198} 199 200void RenderNode::damageSelf(TreeInfo& info) { 201 if (isRenderable()) { 202 if (properties().getClipDamageToBounds()) { 203 info.damageAccumulator->dirty(0, 0, properties().getWidth(), properties().getHeight()); 204 } else { 205 // Hope this is big enough? 206 // TODO: Get this from the display list ops or something 207 info.damageAccumulator->dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); 208 } 209 } 210} 211 212void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) { 213 LayerType layerType = properties().effectiveLayerType(); 214 if (CC_UNLIKELY(layerType == LayerType::RenderLayer)) { 215 // Damage applied so far needs to affect our parent, but does not require 216 // the layer to be updated. So we pop/push here to clear out the current 217 // damage and get a clean state for display list or children updates to 218 // affect, which will require the layer to be updated 219 info.damageAccumulator->popTransform(); 220 info.damageAccumulator->pushTransform(this); 221 if (dirtyMask & DISPLAY_LIST) { 222 damageSelf(info); 223 } 224 } 225} 226 227static OffscreenBuffer* createLayer(RenderState& renderState, uint32_t width, uint32_t height) { 228 return renderState.layerPool().get(renderState, width, height); 229} 230 231static void destroyLayer(OffscreenBuffer* layer) { 232 RenderState& renderState = layer->renderState; 233 renderState.layerPool().putOrDelete(layer); 234} 235 236static bool layerMatchesWidthAndHeight(OffscreenBuffer* layer, int width, int height) { 237 return layer->viewportWidth == (uint32_t) width && layer->viewportHeight == (uint32_t)height; 238} 239 240void RenderNode::pushLayerUpdate(TreeInfo& info) { 241 LayerType layerType = properties().effectiveLayerType(); 242 // If we are not a layer OR we cannot be rendered (eg, view was detached) 243 // we need to destroy any Layers we may have had previously 244 if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable())) { 245 if (CC_UNLIKELY(mLayer)) { 246 destroyLayer(mLayer); 247 mLayer = nullptr; 248 } 249 return; 250 } 251 252 bool transformUpdateNeeded = false; 253 if (!mLayer) { 254 mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight()); 255 damageSelf(info); 256 transformUpdateNeeded = true; 257 } else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) { 258 // TODO: remove now irrelevant, currently enqueued damage (respecting damage ordering) 259 // Or, ideally, maintain damage between frames on node/layer so ordering is always correct 260 RenderState& renderState = mLayer->renderState; 261 if (properties().fitsOnLayer()) { 262 mLayer = renderState.layerPool().resize(mLayer, getWidth(), getHeight()); 263 } else { 264 destroyLayer(mLayer); 265 mLayer = nullptr; 266 } 267 damageSelf(info); 268 transformUpdateNeeded = true; 269 } 270 271 SkRect dirty; 272 info.damageAccumulator->peekAtDirty(&dirty); 273 274 if (!mLayer) { 275 Caches::getInstance().dumpMemoryUsage(); 276 if (info.errorHandler) { 277 std::ostringstream err; 278 err << "Unable to create layer for " << getName(); 279 const int maxTextureSize = Caches::getInstance().maxTextureSize; 280 if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) { 281 err << ", size " << getWidth() << "x" << getHeight() 282 << " exceeds max size " << maxTextureSize; 283 } else { 284 err << ", see logcat for more info"; 285 } 286 info.errorHandler->onError(err.str()); 287 } 288 return; 289 } 290 291 if (transformUpdateNeeded && mLayer) { 292 // update the transform in window of the layer to reset its origin wrt light source position 293 Matrix4 windowTransform; 294 info.damageAccumulator->computeCurrentTransform(&windowTransform); 295 mLayer->setWindowTransform(windowTransform); 296 } 297 298 info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty); 299 300 // There might be prefetched layers that need to be accounted for. 301 // That might be us, so tell CanvasContext that this layer is in the 302 // tree and should not be destroyed. 303 info.canvasContext.markLayerInUse(this); 304} 305 306/** 307 * Traverse down the the draw tree to prepare for a frame. 308 * 309 * MODE_FULL = UI Thread-driven (thus properties must be synced), otherwise RT driven 310 * 311 * While traversing down the tree, functorsNeedLayer flag is set to true if anything that uses the 312 * stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer. 313 */ 314void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) { 315 info.damageAccumulator->pushTransform(this); 316 317 if (info.mode == TreeInfo::MODE_FULL) { 318 pushStagingPropertiesChanges(info); 319 } 320 uint32_t animatorDirtyMask = 0; 321 if (CC_LIKELY(info.runAnimations)) { 322 animatorDirtyMask = mAnimatorManager.animate(info); 323 } 324 325 bool willHaveFunctor = false; 326 if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayList) { 327 willHaveFunctor = !mStagingDisplayList->getFunctors().empty(); 328 } else if (mDisplayList) { 329 willHaveFunctor = !mDisplayList->getFunctors().empty(); 330 } 331 bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence( 332 willHaveFunctor, functorsNeedLayer); 333 334 if (CC_UNLIKELY(mPositionListener.get())) { 335 mPositionListener->onPositionUpdated(*this, info); 336 } 337 338 prepareLayer(info, animatorDirtyMask); 339 if (info.mode == TreeInfo::MODE_FULL) { 340 pushStagingDisplayListChanges(info); 341 } 342 prepareSubTree(info, childFunctorsNeedLayer, mDisplayList); 343 pushLayerUpdate(info); 344 345 if (mDisplayList) { 346 for (auto& vectorDrawable : mDisplayList->getVectorDrawables()) { 347 // If any vector drawable in the display list needs update, damage the node. 348 if (vectorDrawable->isDirty()) { 349 damageSelf(info); 350 } 351 vectorDrawable->setPropertyChangeWillBeConsumed(true); 352 } 353 } 354 355 info.damageAccumulator->popTransform(); 356} 357 358void RenderNode::syncProperties() { 359 mProperties = mStagingProperties; 360} 361 362void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { 363 // Push the animators first so that setupStartValueIfNecessary() is called 364 // before properties() is trampled by stagingProperties(), as they are 365 // required by some animators. 366 if (CC_LIKELY(info.runAnimations)) { 367 mAnimatorManager.pushStaging(); 368 } 369 if (mDirtyPropertyFields) { 370 mDirtyPropertyFields = 0; 371 damageSelf(info); 372 info.damageAccumulator->popTransform(); 373 syncProperties(); 374 // We could try to be clever and only re-damage if the matrix changed. 375 // However, we don't need to worry about that. The cost of over-damaging 376 // here is only going to be a single additional map rect of this node 377 // plus a rect join(). The parent's transform (and up) will only be 378 // performed once. 379 info.damageAccumulator->pushTransform(this); 380 damageSelf(info); 381 } 382} 383 384void RenderNode::syncDisplayList(TreeInfo* info) { 385 // Make sure we inc first so that we don't fluctuate between 0 and 1, 386 // which would thrash the layer cache 387 if (mStagingDisplayList) { 388 for (auto&& child : mStagingDisplayList->getChildren()) { 389 child->renderNode->incParentRefCount(); 390 } 391 } 392 deleteDisplayList(info ? info->observer : nullptr, info); 393 mDisplayList = mStagingDisplayList; 394 mStagingDisplayList = nullptr; 395 if (mDisplayList) { 396 for (auto& iter : mDisplayList->getFunctors()) { 397 (*iter.functor)(DrawGlInfo::kModeSync, nullptr); 398 } 399 for (auto& vectorDrawable : mDisplayList->getVectorDrawables()) { 400 vectorDrawable->syncProperties(); 401 } 402 } 403} 404 405void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) { 406 if (mNeedsDisplayListSync) { 407 mNeedsDisplayListSync = false; 408 // Damage with the old display list first then the new one to catch any 409 // changes in isRenderable or, in the future, bounds 410 damageSelf(info); 411 syncDisplayList(&info); 412 damageSelf(info); 413 } 414} 415 416void RenderNode::deleteDisplayList(TreeObserver* observer, TreeInfo* info) { 417 if (mDisplayList) { 418 for (auto&& child : mDisplayList->getChildren()) { 419 child->renderNode->decParentRefCount(observer, info); 420 } 421 } 422 delete mDisplayList; 423 mDisplayList = nullptr; 424} 425 426void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree) { 427 if (subtree) { 428 TextureCache& cache = Caches::getInstance().textureCache; 429 info.out.hasFunctors |= subtree->getFunctors().size(); 430 for (auto&& bitmapResource : subtree->getBitmapResources()) { 431 void* ownerToken = &info.canvasContext; 432 info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource); 433 } 434 for (auto&& op : subtree->getChildren()) { 435 RenderNode* childNode = op->renderNode; 436 info.damageAccumulator->pushTransform(&op->localMatrix); 437 bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip; 438 childNode->prepareTreeImpl(info, childFunctorsNeedLayer); 439 info.damageAccumulator->popTransform(); 440 } 441 } 442} 443 444void RenderNode::destroyHardwareResources(TreeObserver* observer, TreeInfo* info) { 445 if (mLayer) { 446 destroyLayer(mLayer); 447 mLayer = nullptr; 448 } 449 if (mDisplayList) { 450 for (auto&& child : mDisplayList->getChildren()) { 451 child->renderNode->destroyHardwareResources(observer, info); 452 } 453 if (mNeedsDisplayListSync) { 454 // Next prepare tree we are going to push a new display list, so we can 455 // drop our current one now 456 deleteDisplayList(observer, info); 457 } 458 } 459} 460 461void RenderNode::decParentRefCount(TreeObserver* observer, TreeInfo* info) { 462 LOG_ALWAYS_FATAL_IF(!mParentCount, "already 0!"); 463 mParentCount--; 464 if (!mParentCount) { 465 if (observer) { 466 observer->onMaybeRemovedFromTree(this); 467 } 468 if (CC_UNLIKELY(mPositionListener.get())) { 469 mPositionListener->onPositionLost(*this, info); 470 } 471 // If a child of ours is being attached to our parent then this will incorrectly 472 // destroy its hardware resources. However, this situation is highly unlikely 473 // and the failure is "just" that the layer is re-created, so this should 474 // be safe enough 475 destroyHardwareResources(observer, info); 476 } 477} 478 479/** 480 * Apply property-based transformations to input matrix 481 * 482 * If true3dTransform is set to true, the transform applied to the input matrix will use true 4x4 483 * matrix computation instead of the Skia 3x3 matrix + camera hackery. 484 */ 485void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform) const { 486 if (properties().getLeft() != 0 || properties().getTop() != 0) { 487 matrix.translate(properties().getLeft(), properties().getTop()); 488 } 489 if (properties().getStaticMatrix()) { 490 mat4 stat(*properties().getStaticMatrix()); 491 matrix.multiply(stat); 492 } else if (properties().getAnimationMatrix()) { 493 mat4 anim(*properties().getAnimationMatrix()); 494 matrix.multiply(anim); 495 } 496 497 bool applyTranslationZ = true3dTransform && !MathUtils::isZero(properties().getZ()); 498 if (properties().hasTransformMatrix() || applyTranslationZ) { 499 if (properties().isTransformTranslateOnly()) { 500 matrix.translate(properties().getTranslationX(), properties().getTranslationY(), 501 true3dTransform ? properties().getZ() : 0.0f); 502 } else { 503 if (!true3dTransform) { 504 matrix.multiply(*properties().getTransformMatrix()); 505 } else { 506 mat4 true3dMat; 507 true3dMat.loadTranslate( 508 properties().getPivotX() + properties().getTranslationX(), 509 properties().getPivotY() + properties().getTranslationY(), 510 properties().getZ()); 511 true3dMat.rotate(properties().getRotationX(), 1, 0, 0); 512 true3dMat.rotate(properties().getRotationY(), 0, 1, 0); 513 true3dMat.rotate(properties().getRotation(), 0, 0, 1); 514 true3dMat.scale(properties().getScaleX(), properties().getScaleY(), 1); 515 true3dMat.translate(-properties().getPivotX(), -properties().getPivotY()); 516 517 matrix.multiply(true3dMat); 518 } 519 } 520 } 521} 522 523/** 524 * Organizes the DisplayList hierarchy to prepare for background projection reordering. 525 * 526 * This should be called before a call to defer() or drawDisplayList() 527 * 528 * Each DisplayList that serves as a 3d root builds its list of composited children, 529 * which are flagged to not draw in the standard draw loop. 530 */ 531void RenderNode::computeOrdering() { 532 ATRACE_CALL(); 533 mProjectedNodes.clear(); 534 535 // TODO: create temporary DDLOp and call computeOrderingImpl on top DisplayList so that 536 // transform properties are applied correctly to top level children 537 if (mDisplayList == nullptr) return; 538 for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) { 539 RenderNodeOp* childOp = mDisplayList->getChildren()[i]; 540 childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity()); 541 } 542} 543 544void RenderNode::computeOrderingImpl( 545 RenderNodeOp* opState, 546 std::vector<RenderNodeOp*>* compositedChildrenOfProjectionSurface, 547 const mat4* transformFromProjectionSurface) { 548 mProjectedNodes.clear(); 549 if (mDisplayList == nullptr || mDisplayList->isEmpty()) return; 550 551 // TODO: should avoid this calculation in most cases 552 // TODO: just calculate single matrix, down to all leaf composited elements 553 Matrix4 localTransformFromProjectionSurface(*transformFromProjectionSurface); 554 localTransformFromProjectionSurface.multiply(opState->localMatrix); 555 556 if (properties().getProjectBackwards()) { 557 // composited projectee, flag for out of order draw, save matrix, and store in proj surface 558 opState->skipInOrderDraw = true; 559 opState->transformFromCompositingAncestor = localTransformFromProjectionSurface; 560 compositedChildrenOfProjectionSurface->push_back(opState); 561 } else { 562 // standard in order draw 563 opState->skipInOrderDraw = false; 564 } 565 566 if (mDisplayList->getChildren().size() > 0) { 567 const bool isProjectionReceiver = mDisplayList->projectionReceiveIndex >= 0; 568 bool haveAppliedPropertiesToProjection = false; 569 for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) { 570 RenderNodeOp* childOp = mDisplayList->getChildren()[i]; 571 RenderNode* child = childOp->renderNode; 572 573 std::vector<RenderNodeOp*>* projectionChildren = nullptr; 574 const mat4* projectionTransform = nullptr; 575 if (isProjectionReceiver && !child->properties().getProjectBackwards()) { 576 // if receiving projections, collect projecting descendant 577 578 // Note that if a direct descendant is projecting backwards, we pass its 579 // grandparent projection collection, since it shouldn't project onto its 580 // parent, where it will already be drawing. 581 projectionChildren = &mProjectedNodes; 582 projectionTransform = &mat4::identity(); 583 } else { 584 if (!haveAppliedPropertiesToProjection) { 585 applyViewPropertyTransforms(localTransformFromProjectionSurface); 586 haveAppliedPropertiesToProjection = true; 587 } 588 projectionChildren = compositedChildrenOfProjectionSurface; 589 projectionTransform = &localTransformFromProjectionSurface; 590 } 591 child->computeOrderingImpl(childOp, projectionChildren, projectionTransform); 592 } 593 } 594} 595 596} /* namespace uirenderer */ 597} /* namespace android */ 598