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