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