1/* 2 * Copyright (C) 2006 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 17package android.content.res; 18 19import android.os.ParcelFileDescriptor; 20import android.util.Config; 21import android.util.Log; 22import android.util.TypedValue; 23 24import java.io.FileNotFoundException; 25import java.io.IOException; 26import java.io.InputStream; 27import java.util.HashMap; 28 29/** 30 * Provides access to an application's raw asset files; see {@link Resources} 31 * for the way most applications will want to retrieve their resource data. 32 * This class presents a lower-level API that allows you to open and read raw 33 * files that have been bundled with the application as a simple stream of 34 * bytes. 35 */ 36public final class AssetManager { 37 /* modes used when opening an asset */ 38 39 /** 40 * Mode for {@link #open(String, int)}: no specific information about how 41 * data will be accessed. 42 */ 43 public static final int ACCESS_UNKNOWN = 0; 44 /** 45 * Mode for {@link #open(String, int)}: Read chunks, and seek forward and 46 * backward. 47 */ 48 public static final int ACCESS_RANDOM = 1; 49 /** 50 * Mode for {@link #open(String, int)}: Read sequentially, with an 51 * occasional forward seek. 52 */ 53 public static final int ACCESS_STREAMING = 2; 54 /** 55 * Mode for {@link #open(String, int)}: Attempt to load contents into 56 * memory, for fast small reads. 57 */ 58 public static final int ACCESS_BUFFER = 3; 59 60 private static final String TAG = "AssetManager"; 61 private static final boolean localLOGV = Config.LOGV || false; 62 63 private static final boolean DEBUG_REFS = false; 64 65 private static final Object sSync = new Object(); 66 private static AssetManager sSystem = null; 67 68 private final TypedValue mValue = new TypedValue(); 69 private final long[] mOffsets = new long[2]; 70 71 // For communication with native code. 72 private int mObject; 73 74 private StringBlock mStringBlocks[] = null; 75 76 private int mNumRefs = 1; 77 private boolean mOpen = true; 78 private HashMap<Integer, RuntimeException> mRefStacks; 79 80 /** 81 * Create a new AssetManager containing only the basic system assets. 82 * Applications will not generally use this method, instead retrieving the 83 * appropriate asset manager with {@link Resources#getAssets}. Not for 84 * use by applications. 85 * {@hide} 86 */ 87 public AssetManager() { 88 synchronized (this) { 89 if (DEBUG_REFS) { 90 mNumRefs = 0; 91 incRefsLocked(this.hashCode()); 92 } 93 init(); 94 if (localLOGV) Log.v(TAG, "New asset manager: " + this); 95 ensureSystemAssets(); 96 } 97 } 98 99 private static void ensureSystemAssets() { 100 synchronized (sSync) { 101 if (sSystem == null) { 102 AssetManager system = new AssetManager(true); 103 system.makeStringBlocks(false); 104 sSystem = system; 105 } 106 } 107 } 108 109 private AssetManager(boolean isSystem) { 110 if (DEBUG_REFS) { 111 synchronized (this) { 112 mNumRefs = 0; 113 incRefsLocked(this.hashCode()); 114 } 115 } 116 init(); 117 if (localLOGV) Log.v(TAG, "New asset manager: " + this); 118 } 119 120 /** 121 * Return a global shared asset manager that provides access to only 122 * system assets (no application assets). 123 * {@hide} 124 */ 125 public static AssetManager getSystem() { 126 ensureSystemAssets(); 127 return sSystem; 128 } 129 130 /** 131 * Close this asset manager. 132 */ 133 public void close() { 134 synchronized(this) { 135 //System.out.println("Release: num=" + mNumRefs 136 // + ", released=" + mReleased); 137 if (mOpen) { 138 mOpen = false; 139 decRefsLocked(this.hashCode()); 140 } 141 } 142 } 143 144 /** 145 * Retrieve the string value associated with a particular resource 146 * identifier for the current configuration / skin. 147 */ 148 /*package*/ final CharSequence getResourceText(int ident) { 149 synchronized (this) { 150 TypedValue tmpValue = mValue; 151 int block = loadResourceValue(ident, tmpValue, true); 152 if (block >= 0) { 153 if (tmpValue.type == TypedValue.TYPE_STRING) { 154 return mStringBlocks[block].get(tmpValue.data); 155 } 156 return tmpValue.coerceToString(); 157 } 158 } 159 return null; 160 } 161 162 /** 163 * Retrieve the string value associated with a particular resource 164 * identifier for the current configuration / skin. 165 */ 166 /*package*/ final CharSequence getResourceBagText(int ident, int bagEntryId) { 167 synchronized (this) { 168 TypedValue tmpValue = mValue; 169 int block = loadResourceBagValue(ident, bagEntryId, tmpValue, true); 170 if (block >= 0) { 171 if (tmpValue.type == TypedValue.TYPE_STRING) { 172 return mStringBlocks[block].get(tmpValue.data); 173 } 174 return tmpValue.coerceToString(); 175 } 176 } 177 return null; 178 } 179 180 /** 181 * Retrieve the string array associated with a particular resource 182 * identifier. 183 * @param id Resource id of the string array 184 */ 185 /*package*/ final String[] getResourceStringArray(final int id) { 186 String[] retArray = getArrayStringResource(id); 187 return retArray; 188 } 189 190 191 /*package*/ final boolean getResourceValue(int ident, 192 TypedValue outValue, 193 boolean resolveRefs) 194 { 195 int block = loadResourceValue(ident, outValue, resolveRefs); 196 if (block >= 0) { 197 if (outValue.type != TypedValue.TYPE_STRING) { 198 return true; 199 } 200 outValue.string = mStringBlocks[block].get(outValue.data); 201 return true; 202 } 203 return false; 204 } 205 206 /** 207 * Retrieve the text array associated with a particular resource 208 * identifier. 209 * @param id Resource id of the string array 210 */ 211 /*package*/ final CharSequence[] getResourceTextArray(final int id) { 212 int[] rawInfoArray = getArrayStringInfo(id); 213 int rawInfoArrayLen = rawInfoArray.length; 214 final int infoArrayLen = rawInfoArrayLen / 2; 215 int block; 216 int index; 217 CharSequence[] retArray = new CharSequence[infoArrayLen]; 218 for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) { 219 block = rawInfoArray[i]; 220 index = rawInfoArray[i + 1]; 221 retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null; 222 } 223 return retArray; 224 } 225 226 /*package*/ final boolean getThemeValue(int theme, int ident, 227 TypedValue outValue, boolean resolveRefs) { 228 int block = loadThemeAttributeValue(theme, ident, outValue, resolveRefs); 229 if (block >= 0) { 230 if (outValue.type != TypedValue.TYPE_STRING) { 231 return true; 232 } 233 StringBlock[] blocks = mStringBlocks; 234 if (blocks == null) { 235 ensureStringBlocks(); 236 } 237 outValue.string = blocks[block].get(outValue.data); 238 return true; 239 } 240 return false; 241 } 242 243 /*package*/ final void ensureStringBlocks() { 244 if (mStringBlocks == null) { 245 synchronized (this) { 246 if (mStringBlocks == null) { 247 makeStringBlocks(true); 248 } 249 } 250 } 251 } 252 253 private final void makeStringBlocks(boolean copyFromSystem) { 254 final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0; 255 final int num = getStringBlockCount(); 256 mStringBlocks = new StringBlock[num]; 257 if (localLOGV) Log.v(TAG, "Making string blocks for " + this 258 + ": " + num); 259 for (int i=0; i<num; i++) { 260 if (i < sysNum) { 261 mStringBlocks[i] = sSystem.mStringBlocks[i]; 262 } else { 263 mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); 264 } 265 } 266 } 267 268 /*package*/ final CharSequence getPooledString(int block, int id) { 269 //System.out.println("Get pooled: block=" + block 270 // + ", id=#" + Integer.toHexString(id) 271 // + ", blocks=" + mStringBlocks); 272 return mStringBlocks[block-1].get(id); 273 } 274 275 /** 276 * Open an asset using ACCESS_STREAMING mode. This provides access to 277 * files that have been bundled with an application as assets -- that is, 278 * files placed in to the "assets" directory. 279 * 280 * @param fileName The name of the asset to open. This name can be 281 * hierarchical. 282 * 283 * @see #open(String, int) 284 * @see #list 285 */ 286 public final InputStream open(String fileName) throws IOException { 287 return open(fileName, ACCESS_STREAMING); 288 } 289 290 /** 291 * Open an asset using an explicit access mode, returning an InputStream to 292 * read its contents. This provides access to files that have been bundled 293 * with an application as assets -- that is, files placed in to the 294 * "assets" directory. 295 * 296 * @param fileName The name of the asset to open. This name can be 297 * hierarchical. 298 * @param accessMode Desired access mode for retrieving the data. 299 * 300 * @see #ACCESS_UNKNOWN 301 * @see #ACCESS_STREAMING 302 * @see #ACCESS_RANDOM 303 * @see #ACCESS_BUFFER 304 * @see #open(String) 305 * @see #list 306 */ 307 public final InputStream open(String fileName, int accessMode) 308 throws IOException { 309 synchronized (this) { 310 if (!mOpen) { 311 throw new RuntimeException("Assetmanager has been closed"); 312 } 313 int asset = openAsset(fileName, accessMode); 314 if (asset != 0) { 315 AssetInputStream res = new AssetInputStream(asset); 316 incRefsLocked(res.hashCode()); 317 return res; 318 } 319 } 320 throw new FileNotFoundException("Asset file: " + fileName); 321 } 322 323 public final AssetFileDescriptor openFd(String fileName) 324 throws IOException { 325 synchronized (this) { 326 if (!mOpen) { 327 throw new RuntimeException("Assetmanager has been closed"); 328 } 329 ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets); 330 if (pfd != null) { 331 return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); 332 } 333 } 334 throw new FileNotFoundException("Asset file: " + fileName); 335 } 336 337 /** 338 * Return a String array of all the assets at the given path. 339 * 340 * @param path A relative path within the assets, i.e., "docs/home.html". 341 * 342 * @return String[] Array of strings, one for each asset. These file 343 * names are relative to 'path'. You can open the file by 344 * concatenating 'path' and a name in the returned string (via 345 * File) and passing that to open(). 346 * 347 * @see #open 348 */ 349 public native final String[] list(String path) 350 throws IOException; 351 352 /** 353 * {@hide} 354 * Open a non-asset file as an asset using ACCESS_STREAMING mode. This 355 * provides direct access to all of the files included in an application 356 * package (not only its assets). Applications should not normally use 357 * this. 358 * 359 * @see #open(String) 360 */ 361 public final InputStream openNonAsset(String fileName) throws IOException { 362 return openNonAsset(0, fileName, ACCESS_STREAMING); 363 } 364 365 /** 366 * {@hide} 367 * Open a non-asset file as an asset using a specific access mode. This 368 * provides direct access to all of the files included in an application 369 * package (not only its assets). Applications should not normally use 370 * this. 371 * 372 * @see #open(String, int) 373 */ 374 public final InputStream openNonAsset(String fileName, int accessMode) 375 throws IOException { 376 return openNonAsset(0, fileName, accessMode); 377 } 378 379 /** 380 * {@hide} 381 * Open a non-asset in a specified package. Not for use by applications. 382 * 383 * @param cookie Identifier of the package to be opened. 384 * @param fileName Name of the asset to retrieve. 385 */ 386 public final InputStream openNonAsset(int cookie, String fileName) 387 throws IOException { 388 return openNonAsset(cookie, fileName, ACCESS_STREAMING); 389 } 390 391 /** 392 * {@hide} 393 * Open a non-asset in a specified package. Not for use by applications. 394 * 395 * @param cookie Identifier of the package to be opened. 396 * @param fileName Name of the asset to retrieve. 397 * @param accessMode Desired access mode for retrieving the data. 398 */ 399 public final InputStream openNonAsset(int cookie, String fileName, int accessMode) 400 throws IOException { 401 synchronized (this) { 402 if (!mOpen) { 403 throw new RuntimeException("Assetmanager has been closed"); 404 } 405 int asset = openNonAssetNative(cookie, fileName, accessMode); 406 if (asset != 0) { 407 AssetInputStream res = new AssetInputStream(asset); 408 incRefsLocked(res.hashCode()); 409 return res; 410 } 411 } 412 throw new FileNotFoundException("Asset absolute file: " + fileName); 413 } 414 415 public final AssetFileDescriptor openNonAssetFd(String fileName) 416 throws IOException { 417 return openNonAssetFd(0, fileName); 418 } 419 420 public final AssetFileDescriptor openNonAssetFd(int cookie, 421 String fileName) throws IOException { 422 synchronized (this) { 423 if (!mOpen) { 424 throw new RuntimeException("Assetmanager has been closed"); 425 } 426 ParcelFileDescriptor pfd = openNonAssetFdNative(cookie, 427 fileName, mOffsets); 428 if (pfd != null) { 429 return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); 430 } 431 } 432 throw new FileNotFoundException("Asset absolute file: " + fileName); 433 } 434 435 /** 436 * Retrieve a parser for a compiled XML file. 437 * 438 * @param fileName The name of the file to retrieve. 439 */ 440 public final XmlResourceParser openXmlResourceParser(String fileName) 441 throws IOException { 442 return openXmlResourceParser(0, fileName); 443 } 444 445 /** 446 * Retrieve a parser for a compiled XML file. 447 * 448 * @param cookie Identifier of the package to be opened. 449 * @param fileName The name of the file to retrieve. 450 */ 451 public final XmlResourceParser openXmlResourceParser(int cookie, 452 String fileName) throws IOException { 453 XmlBlock block = openXmlBlockAsset(cookie, fileName); 454 XmlResourceParser rp = block.newParser(); 455 block.close(); 456 return rp; 457 } 458 459 /** 460 * {@hide} 461 * Retrieve a non-asset as a compiled XML file. Not for use by 462 * applications. 463 * 464 * @param fileName The name of the file to retrieve. 465 */ 466 /*package*/ final XmlBlock openXmlBlockAsset(String fileName) 467 throws IOException { 468 return openXmlBlockAsset(0, fileName); 469 } 470 471 /** 472 * {@hide} 473 * Retrieve a non-asset as a compiled XML file. Not for use by 474 * applications. 475 * 476 * @param cookie Identifier of the package to be opened. 477 * @param fileName Name of the asset to retrieve. 478 */ 479 /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName) 480 throws IOException { 481 synchronized (this) { 482 if (!mOpen) { 483 throw new RuntimeException("Assetmanager has been closed"); 484 } 485 int xmlBlock = openXmlAssetNative(cookie, fileName); 486 if (xmlBlock != 0) { 487 XmlBlock res = new XmlBlock(this, xmlBlock); 488 incRefsLocked(res.hashCode()); 489 return res; 490 } 491 } 492 throw new FileNotFoundException("Asset XML file: " + fileName); 493 } 494 495 /*package*/ void xmlBlockGone(int id) { 496 synchronized (this) { 497 decRefsLocked(id); 498 } 499 } 500 501 /*package*/ final int createTheme() { 502 synchronized (this) { 503 if (!mOpen) { 504 throw new RuntimeException("Assetmanager has been closed"); 505 } 506 int res = newTheme(); 507 incRefsLocked(res); 508 return res; 509 } 510 } 511 512 /*package*/ final void releaseTheme(int theme) { 513 synchronized (this) { 514 deleteTheme(theme); 515 decRefsLocked(theme); 516 } 517 } 518 519 protected void finalize() throws Throwable { 520 try { 521 if (DEBUG_REFS && mNumRefs != 0) { 522 Log.w(TAG, "AssetManager " + this 523 + " finalized with non-zero refs: " + mNumRefs); 524 if (mRefStacks != null) { 525 for (RuntimeException e : mRefStacks.values()) { 526 Log.w(TAG, "Reference from here", e); 527 } 528 } 529 } 530 destroy(); 531 } finally { 532 super.finalize(); 533 } 534 } 535 536 public final class AssetInputStream extends InputStream { 537 public final int getAssetInt() { 538 return mAsset; 539 } 540 private AssetInputStream(int asset) 541 { 542 mAsset = asset; 543 mLength = getAssetLength(asset); 544 } 545 public final int read() throws IOException { 546 return readAssetChar(mAsset); 547 } 548 public final boolean markSupported() { 549 return true; 550 } 551 public final int available() throws IOException { 552 long len = getAssetRemainingLength(mAsset); 553 return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len; 554 } 555 public final void close() throws IOException { 556 synchronized (AssetManager.this) { 557 if (mAsset != 0) { 558 destroyAsset(mAsset); 559 mAsset = 0; 560 decRefsLocked(hashCode()); 561 } 562 } 563 } 564 public final void mark(int readlimit) { 565 mMarkPos = seekAsset(mAsset, 0, 0); 566 } 567 public final void reset() throws IOException { 568 seekAsset(mAsset, mMarkPos, -1); 569 } 570 public final int read(byte[] b) throws IOException { 571 return readAsset(mAsset, b, 0, b.length); 572 } 573 public final int read(byte[] b, int off, int len) throws IOException { 574 return readAsset(mAsset, b, off, len); 575 } 576 public final long skip(long n) throws IOException { 577 long pos = seekAsset(mAsset, 0, 0); 578 if ((pos+n) > mLength) { 579 n = mLength-pos; 580 } 581 if (n > 0) { 582 seekAsset(mAsset, n, 0); 583 } 584 return n; 585 } 586 587 protected void finalize() throws Throwable 588 { 589 close(); 590 } 591 592 private int mAsset; 593 private long mLength; 594 private long mMarkPos; 595 } 596 597 /** 598 * Add an additional set of assets to the asset manager. This can be 599 * either a directory or ZIP file. Not for use by applications. Returns 600 * the cookie of the added asset, or 0 on failure. 601 * {@hide} 602 */ 603 public native final int addAssetPath(String path); 604 605 /** 606 * Add multiple sets of assets to the asset manager at once. See 607 * {@link #addAssetPath(String)} for more information. Returns array of 608 * cookies for each added asset with 0 indicating failure, or null if 609 * the input array of paths is null. 610 * {@hide} 611 */ 612 public final int[] addAssetPaths(String[] paths) { 613 if (paths == null) { 614 return null; 615 } 616 617 int[] cookies = new int[paths.length]; 618 for (int i = 0; i < paths.length; i++) { 619 cookies[i] = addAssetPath(paths[i]); 620 } 621 622 return cookies; 623 } 624 625 /** 626 * Determine whether the state in this asset manager is up-to-date with 627 * the files on the filesystem. If false is returned, you need to 628 * instantiate a new AssetManager class to see the new data. 629 * {@hide} 630 */ 631 public native final boolean isUpToDate(); 632 633 /** 634 * Change the locale being used by this asset manager. Not for use by 635 * applications. 636 * {@hide} 637 */ 638 public native final void setLocale(String locale); 639 640 /** 641 * Get the locales that this asset manager contains data for. 642 */ 643 public native final String[] getLocales(); 644 645 /** 646 * Change the configuation used when retrieving resources. Not for use by 647 * applications. 648 * {@hide} 649 */ 650 public native final void setConfiguration(int mcc, int mnc, String locale, 651 int orientation, int touchscreen, int density, int keyboard, 652 int keyboardHidden, int navigation, int screenWidth, int screenHeight, 653 int screenLayout, int uiMode, int majorVersion); 654 655 /** 656 * Retrieve the resource identifier for the given resource name. 657 */ 658 /*package*/ native final int getResourceIdentifier(String type, 659 String name, 660 String defPackage); 661 662 /*package*/ native final String getResourceName(int resid); 663 /*package*/ native final String getResourcePackageName(int resid); 664 /*package*/ native final String getResourceTypeName(int resid); 665 /*package*/ native final String getResourceEntryName(int resid); 666 667 private native final int openAsset(String fileName, int accessMode); 668 private final native ParcelFileDescriptor openAssetFd(String fileName, 669 long[] outOffsets) throws IOException; 670 private native final int openNonAssetNative(int cookie, String fileName, 671 int accessMode); 672 private native ParcelFileDescriptor openNonAssetFdNative(int cookie, 673 String fileName, long[] outOffsets) throws IOException; 674 private native final void destroyAsset(int asset); 675 private native final int readAssetChar(int asset); 676 private native final int readAsset(int asset, byte[] b, int off, int len); 677 private native final long seekAsset(int asset, long offset, int whence); 678 private native final long getAssetLength(int asset); 679 private native final long getAssetRemainingLength(int asset); 680 681 /** Returns true if the resource was found, filling in mRetStringBlock and 682 * mRetData. */ 683 private native final int loadResourceValue(int ident, TypedValue outValue, 684 boolean resolve); 685 /** Returns true if the resource was found, filling in mRetStringBlock and 686 * mRetData. */ 687 private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, 688 boolean resolve); 689 /*package*/ static final int STYLE_NUM_ENTRIES = 6; 690 /*package*/ static final int STYLE_TYPE = 0; 691 /*package*/ static final int STYLE_DATA = 1; 692 /*package*/ static final int STYLE_ASSET_COOKIE = 2; 693 /*package*/ static final int STYLE_RESOURCE_ID = 3; 694 /*package*/ static final int STYLE_CHANGING_CONFIGURATIONS = 4; 695 /*package*/ static final int STYLE_DENSITY = 5; 696 /*package*/ native static final boolean applyStyle(int theme, 697 int defStyleAttr, int defStyleRes, int xmlParser, 698 int[] inAttrs, int[] outValues, int[] outIndices); 699 /*package*/ native final boolean retrieveAttributes( 700 int xmlParser, int[] inAttrs, int[] outValues, int[] outIndices); 701 /*package*/ native final int getArraySize(int resource); 702 /*package*/ native final int retrieveArray(int resource, int[] outValues); 703 private native final int getStringBlockCount(); 704 private native final int getNativeStringBlock(int block); 705 706 /** 707 * {@hide} 708 */ 709 public native final String getCookieName(int cookie); 710 711 /** 712 * {@hide} 713 */ 714 public native static final int getGlobalAssetCount(); 715 716 /** 717 * {@hide} 718 */ 719 public native static final String getAssetAllocations(); 720 721 /** 722 * {@hide} 723 */ 724 public native static final int getGlobalAssetManagerCount(); 725 726 private native final int newTheme(); 727 private native final void deleteTheme(int theme); 728 /*package*/ native static final void applyThemeStyle(int theme, int styleRes, boolean force); 729 /*package*/ native static final void copyTheme(int dest, int source); 730 /*package*/ native static final int loadThemeAttributeValue(int theme, int ident, 731 TypedValue outValue, 732 boolean resolve); 733 /*package*/ native static final void dumpTheme(int theme, int priority, String tag, String prefix); 734 735 private native final int openXmlAssetNative(int cookie, String fileName); 736 737 private native final String[] getArrayStringResource(int arrayRes); 738 private native final int[] getArrayStringInfo(int arrayRes); 739 /*package*/ native final int[] getArrayIntResource(int arrayRes); 740 741 private native final void init(); 742 private native final void destroy(); 743 744 private final void incRefsLocked(int id) { 745 if (DEBUG_REFS) { 746 if (mRefStacks == null) { 747 mRefStacks = new HashMap<Integer, RuntimeException>(); 748 RuntimeException ex = new RuntimeException(); 749 ex.fillInStackTrace(); 750 mRefStacks.put(this.hashCode(), ex); 751 } 752 } 753 mNumRefs++; 754 } 755 756 private final void decRefsLocked(int id) { 757 if (DEBUG_REFS && mRefStacks != null) { 758 mRefStacks.remove(id); 759 } 760 mNumRefs--; 761 //System.out.println("Dec streams: mNumRefs=" + mNumRefs 762 // + " mReleased=" + mReleased); 763 if (mNumRefs == 0) { 764 destroy(); 765 } 766 } 767} 768