• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2006 The Android Open Source Project
3 //
4 // Information about assets being operated on.
5 //
6 #ifndef __AAPT_ASSETS_H
7 #define __AAPT_ASSETS_H
8 
9 #include <androidfw/AssetManager.h>
10 #include <androidfw/ResourceTypes.h>
11 #include <stdlib.h>
12 #include <set>
13 #include <utils/KeyedVector.h>
14 #include <utils/RefBase.h>
15 #include <utils/SortedVector.h>
16 #include <utils/String8.h>
17 #include <utils/Vector.h>
18 
19 #include "AaptConfig.h"
20 #include "Bundle.h"
21 #include "ConfigDescription.h"
22 #include "SourcePos.h"
23 #include "ZipFile.h"
24 
25 using namespace android;
26 
27 extern const char * const gDefaultIgnoreAssets;
28 extern const char * gUserIgnoreAssets;
29 
30 bool valid_symbol_name(const String8& str);
31 
32 class AaptAssets;
33 
34 enum {
35     AXIS_NONE = 0,
36     AXIS_MCC = 1,
37     AXIS_MNC,
38     AXIS_LOCALE,
39     AXIS_SCREENLAYOUTSIZE,
40     AXIS_SCREENLAYOUTLONG,
41     AXIS_ORIENTATION,
42     AXIS_UIMODETYPE,
43     AXIS_UIMODENIGHT,
44     AXIS_DENSITY,
45     AXIS_TOUCHSCREEN,
46     AXIS_KEYSHIDDEN,
47     AXIS_KEYBOARD,
48     AXIS_NAVHIDDEN,
49     AXIS_NAVIGATION,
50     AXIS_SCREENSIZE,
51     AXIS_SMALLESTSCREENWIDTHDP,
52     AXIS_SCREENWIDTHDP,
53     AXIS_SCREENHEIGHTDP,
54     AXIS_LAYOUTDIR,
55     AXIS_VERSION,
56 
57     AXIS_START = AXIS_MCC,
58     AXIS_END = AXIS_VERSION,
59 };
60 
61 struct AaptLocaleValue {
62      char language[4];
63      char region[4];
64      char script[4];
65      char variant[8];
66 
AaptLocaleValueAaptLocaleValue67      AaptLocaleValue() {
68          memset(this, 0, sizeof(AaptLocaleValue));
69      }
70 
71      // Initialize this AaptLocaleValue from a config string.
72      bool initFromFilterString(const String8& config);
73 
74      int initFromDirName(const Vector<String8>& parts, const int startIndex);
75 
76      // Initialize this AaptLocaleValue from a ResTable_config.
77      void initFromResTable(const ResTable_config& config);
78 
79      void writeTo(ResTable_config* out) const;
80 
compareAaptLocaleValue81      int compare(const AaptLocaleValue& other) const {
82          return memcmp(this, &other, sizeof(AaptLocaleValue));
83      }
84 
85      inline bool operator<(const AaptLocaleValue& o) const { return compare(o) < 0; }
86      inline bool operator<=(const AaptLocaleValue& o) const { return compare(o) <= 0; }
87      inline bool operator==(const AaptLocaleValue& o) const { return compare(o) == 0; }
88      inline bool operator!=(const AaptLocaleValue& o) const { return compare(o) != 0; }
89      inline bool operator>=(const AaptLocaleValue& o) const { return compare(o) >= 0; }
90      inline bool operator>(const AaptLocaleValue& o) const { return compare(o) > 0; }
91 private:
92      void setLanguage(const char* language);
93      void setRegion(const char* language);
94      void setScript(const char* script);
95      void setVariant(const char* variant);
96 };
97 
98 /**
99  * This structure contains a specific variation of a single file out
100  * of all the variations it can have that we can have.
101  */
102 struct AaptGroupEntry
103 {
104 public:
AaptGroupEntryAaptGroupEntry105     AaptGroupEntry() {}
AaptGroupEntryAaptGroupEntry106     AaptGroupEntry(const ConfigDescription& config) : mParams(config) {}
107 
108     bool initFromDirName(const char* dir, String8* resType);
109 
toParamsAaptGroupEntry110     inline const ConfigDescription& toParams() const { return mParams; }
111 
compareAaptGroupEntry112     inline int compare(const AaptGroupEntry& o) const { return mParams.compareLogical(o.mParams); }
113     inline bool operator<(const AaptGroupEntry& o) const { return compare(o) < 0; }
114     inline bool operator<=(const AaptGroupEntry& o) const { return compare(o) <= 0; }
115     inline bool operator==(const AaptGroupEntry& o) const { return compare(o) == 0; }
116     inline bool operator!=(const AaptGroupEntry& o) const { return compare(o) != 0; }
117     inline bool operator>=(const AaptGroupEntry& o) const { return compare(o) >= 0; }
118     inline bool operator>(const AaptGroupEntry& o) const { return compare(o) > 0; }
119 
toStringAaptGroupEntry120     String8 toString() const { return mParams.toString(); }
121     String8 toDirName(const String8& resType) const;
122 
getVersionStringAaptGroupEntry123     const String8 getVersionString() const { return AaptConfig::getVersion(mParams); }
124 
125 private:
126     ConfigDescription mParams;
127 };
128 
compare_type(const AaptGroupEntry & lhs,const AaptGroupEntry & rhs)129 inline int compare_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs)
130 {
131     return lhs.compare(rhs);
132 }
133 
strictly_order_type(const AaptGroupEntry & lhs,const AaptGroupEntry & rhs)134 inline int strictly_order_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs)
135 {
136     return compare_type(lhs, rhs) < 0;
137 }
138 
139 class AaptGroup;
140 class FilePathStore;
141 
142 /**
143  * A single asset file we know about.
144  */
145 class AaptFile : public RefBase
146 {
147 public:
AaptFile(const String8 & sourceFile,const AaptGroupEntry & groupEntry,const String8 & resType)148     AaptFile(const String8& sourceFile, const AaptGroupEntry& groupEntry,
149              const String8& resType)
150         : mGroupEntry(groupEntry)
151         , mResourceType(resType)
152         , mSourceFile(sourceFile)
153         , mData(NULL)
154         , mDataSize(0)
155         , mBufferSize(0)
156         , mCompression(ZipEntry::kCompressStored)
157         {
158             //printf("new AaptFile created %s\n", (const char*)sourceFile);
159         }
~AaptFile()160     virtual ~AaptFile() {
161         free(mData);
162     }
163 
getPath()164     const String8& getPath() const { return mPath; }
getGroupEntry()165     const AaptGroupEntry& getGroupEntry() const { return mGroupEntry; }
166 
167     // Data API.  If there is data attached to the file,
168     // getSourceFile() is not used.
hasData()169     bool hasData() const { return mData != NULL; }
getData()170     const void* getData() const { return mData; }
getSize()171     size_t getSize() const { return mDataSize; }
172     void* editData(size_t size);
173     void* editData(size_t* outSize = NULL);
174     void* editDataInRange(size_t offset, size_t size);
175     void* padData(size_t wordSize);
176     status_t writeData(const void* data, size_t size);
177     void clearData();
178 
getResourceType()179     const String8& getResourceType() const { return mResourceType; }
180 
181     // File API.  If the file does not hold raw data, this is
182     // a full path to a file on the filesystem that holds its data.
getSourceFile()183     const String8& getSourceFile() const { return mSourceFile; }
184 
185     String8 getPrintableSource() const;
186 
187     // Desired compression method, as per utils/ZipEntry.h.  For example,
188     // no compression is ZipEntry::kCompressStored.
getCompressionMethod()189     int getCompressionMethod() const { return mCompression; }
setCompressionMethod(int c)190     void setCompressionMethod(int c) { mCompression = c; }
191 private:
192     friend class AaptGroup;
193 
194     String8 mPath;
195     AaptGroupEntry mGroupEntry;
196     String8 mResourceType;
197     String8 mSourceFile;
198     void* mData;
199     size_t mDataSize;
200     size_t mBufferSize;
201     int mCompression;
202 };
203 
204 /**
205  * A group of related files (the same file, with different
206  * vendor/locale variations).
207  */
208 class AaptGroup : public RefBase
209 {
210 public:
AaptGroup(const String8 & leaf,const String8 & path)211     AaptGroup(const String8& leaf, const String8& path)
212         : mLeaf(leaf), mPath(path) { }
~AaptGroup()213     virtual ~AaptGroup() { }
214 
getLeaf()215     const String8& getLeaf() const { return mLeaf; }
216 
217     // Returns the relative path after the AaptGroupEntry dirs.
getPath()218     const String8& getPath() const { return mPath; }
219 
getFiles()220     const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& getFiles() const
221         { return mFiles; }
222 
223     status_t addFile(const sp<AaptFile>& file, const bool overwriteDuplicate=false);
224     void removeFile(size_t index);
225 
226     void print(const String8& prefix) const;
227 
228     String8 getPrintableSource() const;
229 
230 private:
231     String8 mLeaf;
232     String8 mPath;
233 
234     DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > mFiles;
235 };
236 
237 /**
238  * A single directory of assets, which can contain files and other
239  * sub-directories.
240  */
241 class AaptDir : public RefBase
242 {
243 public:
AaptDir(const String8 & leaf,const String8 & path)244     AaptDir(const String8& leaf, const String8& path)
245         : mLeaf(leaf), mPath(path) { }
~AaptDir()246     virtual ~AaptDir() { }
247 
getLeaf()248     const String8& getLeaf() const { return mLeaf; }
249 
getPath()250     const String8& getPath() const { return mPath; }
251 
getFiles()252     const DefaultKeyedVector<String8, sp<AaptGroup> >& getFiles() const { return mFiles; }
getDirs()253     const DefaultKeyedVector<String8, sp<AaptDir> >& getDirs() const { return mDirs; }
254 
255     virtual status_t addFile(const String8& name, const sp<AaptGroup>& file);
256 
257     void removeFile(const String8& name);
258     void removeDir(const String8& name);
259 
260     /*
261      * Perform some sanity checks on the names of files and directories here.
262      * In particular:
263      *  - Check for illegal chars in filenames.
264      *  - Check filename length.
265      *  - Check for presence of ".gz" and non-".gz" copies of same file.
266      *  - Check for multiple files whose names match in a case-insensitive
267      *    fashion (problematic for some systems).
268      *
269      * Comparing names against all other names is O(n^2).  We could speed
270      * it up some by sorting the entries and being smarter about what we
271      * compare against, but I'm not expecting to have enough files in a
272      * single directory to make a noticeable difference in speed.
273      *
274      * Note that sorting here is not enough to guarantee that the package
275      * contents are sorted -- subsequent updates can rearrange things.
276      */
277     status_t validate() const;
278 
279     void print(const String8& prefix) const;
280 
281     String8 getPrintableSource() const;
282 
283 private:
284     friend class AaptAssets;
285 
286     status_t addDir(const String8& name, const sp<AaptDir>& dir);
287     sp<AaptDir> makeDir(const String8& name);
288     status_t addLeafFile(const String8& leafName,
289                          const sp<AaptFile>& file,
290                          const bool overwrite=false);
291     virtual ssize_t slurpFullTree(Bundle* bundle,
292                                   const String8& srcDir,
293                                   const AaptGroupEntry& kind,
294                                   const String8& resType,
295                                   sp<FilePathStore>& fullResPaths,
296                                   const bool overwrite=false);
297 
298     String8 mLeaf;
299     String8 mPath;
300 
301     DefaultKeyedVector<String8, sp<AaptGroup> > mFiles;
302     DefaultKeyedVector<String8, sp<AaptDir> > mDirs;
303 };
304 
305 /**
306  * All information we know about a particular symbol.
307  */
308 class AaptSymbolEntry
309 {
310 public:
AaptSymbolEntry()311     AaptSymbolEntry()
312         : isPublic(false), isJavaSymbol(false), typeCode(TYPE_UNKNOWN)
313     {
314     }
AaptSymbolEntry(const String8 & _name)315     AaptSymbolEntry(const String8& _name)
316         : name(_name), isPublic(false), isJavaSymbol(false), typeCode(TYPE_UNKNOWN)
317     {
318     }
AaptSymbolEntry(const AaptSymbolEntry & o)319     AaptSymbolEntry(const AaptSymbolEntry& o)
320         : name(o.name), sourcePos(o.sourcePos), isPublic(o.isPublic)
321         , isJavaSymbol(o.isJavaSymbol), comment(o.comment), typeComment(o.typeComment)
322         , typeCode(o.typeCode), int32Val(o.int32Val), stringVal(o.stringVal)
323     {
324     }
325     AaptSymbolEntry operator=(const AaptSymbolEntry& o)
326     {
327         sourcePos = o.sourcePos;
328         isPublic = o.isPublic;
329         isJavaSymbol = o.isJavaSymbol;
330         comment = o.comment;
331         typeComment = o.typeComment;
332         typeCode = o.typeCode;
333         int32Val = o.int32Val;
334         stringVal = o.stringVal;
335         return *this;
336     }
337 
338     const String8 name;
339 
340     SourcePos sourcePos;
341     bool isPublic;
342     bool isJavaSymbol;
343 
344     String16 comment;
345     String16 typeComment;
346 
347     enum {
348         TYPE_UNKNOWN = 0,
349         TYPE_INT32,
350         TYPE_STRING
351     };
352 
353     int typeCode;
354 
355     // Value.  May be one of these.
356     int32_t int32Val;
357     String8 stringVal;
358 };
359 
360 /**
361  * A group of related symbols (such as indices into a string block)
362  * that have been generated from the assets.
363  */
364 class AaptSymbols : public RefBase
365 {
366 public:
AaptSymbols()367     AaptSymbols() { }
~AaptSymbols()368     virtual ~AaptSymbols() { }
369 
addSymbol(const String8 & name,int32_t value,const SourcePos & pos)370     status_t addSymbol(const String8& name, int32_t value, const SourcePos& pos) {
371         if (!check_valid_symbol_name(name, pos, "symbol")) {
372             return BAD_VALUE;
373         }
374         AaptSymbolEntry& sym = edit_symbol(name, &pos);
375         sym.typeCode = AaptSymbolEntry::TYPE_INT32;
376         sym.int32Val = value;
377         return NO_ERROR;
378     }
379 
addStringSymbol(const String8 & name,const String8 & value,const SourcePos & pos)380     status_t addStringSymbol(const String8& name, const String8& value,
381             const SourcePos& pos) {
382         if (!check_valid_symbol_name(name, pos, "symbol")) {
383             return BAD_VALUE;
384         }
385         AaptSymbolEntry& sym = edit_symbol(name, &pos);
386         sym.typeCode = AaptSymbolEntry::TYPE_STRING;
387         sym.stringVal = value;
388         return NO_ERROR;
389     }
390 
makeSymbolPublic(const String8 & name,const SourcePos & pos)391     status_t makeSymbolPublic(const String8& name, const SourcePos& pos) {
392         if (!check_valid_symbol_name(name, pos, "symbol")) {
393             return BAD_VALUE;
394         }
395         AaptSymbolEntry& sym = edit_symbol(name, &pos);
396         sym.isPublic = true;
397         return NO_ERROR;
398     }
399 
makeSymbolJavaSymbol(const String8 & name,const SourcePos & pos)400     status_t makeSymbolJavaSymbol(const String8& name, const SourcePos& pos) {
401         if (!check_valid_symbol_name(name, pos, "symbol")) {
402             return BAD_VALUE;
403         }
404         AaptSymbolEntry& sym = edit_symbol(name, &pos);
405         sym.isJavaSymbol = true;
406         return NO_ERROR;
407     }
408 
appendComment(const String8 & name,const String16 & comment,const SourcePos & pos)409     void appendComment(const String8& name, const String16& comment, const SourcePos& pos) {
410         if (comment.size() <= 0) {
411             return;
412         }
413         AaptSymbolEntry& sym = edit_symbol(name, &pos);
414         if (sym.comment.size() == 0) {
415             sym.comment = comment;
416         } else {
417             sym.comment.append(String16("\n"));
418             sym.comment.append(comment);
419         }
420     }
421 
appendTypeComment(const String8 & name,const String16 & comment)422     void appendTypeComment(const String8& name, const String16& comment) {
423         if (comment.size() <= 0) {
424             return;
425         }
426         AaptSymbolEntry& sym = edit_symbol(name, NULL);
427         if (sym.typeComment.size() == 0) {
428             sym.typeComment = comment;
429         } else {
430             sym.typeComment.append(String16("\n"));
431             sym.typeComment.append(comment);
432         }
433     }
434 
addNestedSymbol(const String8 & name,const SourcePos & pos)435     sp<AaptSymbols> addNestedSymbol(const String8& name, const SourcePos& pos) {
436         if (!check_valid_symbol_name(name, pos, "nested symbol")) {
437             return NULL;
438         }
439 
440         sp<AaptSymbols> sym = mNestedSymbols.valueFor(name);
441         if (sym == NULL) {
442             sym = new AaptSymbols();
443             mNestedSymbols.add(name, sym);
444         }
445 
446         return sym;
447     }
448 
449     status_t applyJavaSymbols(const sp<AaptSymbols>& javaSymbols);
450 
getSymbols()451     const KeyedVector<String8, AaptSymbolEntry>& getSymbols() const
452         { return mSymbols; }
getNestedSymbols()453     const DefaultKeyedVector<String8, sp<AaptSymbols> >& getNestedSymbols() const
454         { return mNestedSymbols; }
455 
getComment(const String8 & name)456     const String16& getComment(const String8& name) const
457         { return get_symbol(name).comment; }
getTypeComment(const String8 & name)458     const String16& getTypeComment(const String8& name) const
459         { return get_symbol(name).typeComment; }
460 
461 private:
check_valid_symbol_name(const String8 & symbol,const SourcePos & pos,const char * label)462     bool check_valid_symbol_name(const String8& symbol, const SourcePos& pos, const char* label) {
463         if (valid_symbol_name(symbol)) {
464             return true;
465         }
466         pos.error("invalid %s: '%s'\n", label, symbol.string());
467         return false;
468     }
edit_symbol(const String8 & symbol,const SourcePos * pos)469     AaptSymbolEntry& edit_symbol(const String8& symbol, const SourcePos* pos) {
470         ssize_t i = mSymbols.indexOfKey(symbol);
471         if (i < 0) {
472             i = mSymbols.add(symbol, AaptSymbolEntry(symbol));
473         }
474         AaptSymbolEntry& sym = mSymbols.editValueAt(i);
475         if (pos != NULL && sym.sourcePos.line < 0) {
476             sym.sourcePos = *pos;
477         }
478         return sym;
479     }
get_symbol(const String8 & symbol)480     const AaptSymbolEntry& get_symbol(const String8& symbol) const {
481         ssize_t i = mSymbols.indexOfKey(symbol);
482         if (i >= 0) {
483             return mSymbols.valueAt(i);
484         }
485         return mDefSymbol;
486     }
487 
488     KeyedVector<String8, AaptSymbolEntry>           mSymbols;
489     DefaultKeyedVector<String8, sp<AaptSymbols> >   mNestedSymbols;
490     AaptSymbolEntry                                 mDefSymbol;
491 };
492 
493 class ResourceTypeSet : public RefBase,
494                         public KeyedVector<String8,sp<AaptGroup> >
495 {
496 public:
497     ResourceTypeSet();
498 };
499 
500 // Storage for lists of fully qualified paths for
501 // resources encountered during slurping.
502 class FilePathStore : public RefBase,
503                       public Vector<String8>
504 {
505 public:
506     FilePathStore();
507 };
508 
509 /**
510  * Asset hierarchy being operated on.
511  */
512 class AaptAssets : public AaptDir
513 {
514 public:
515     AaptAssets();
~AaptAssets()516     virtual ~AaptAssets() { delete mRes; }
517 
getPackage()518     const String8& getPackage() const { return mPackage; }
setPackage(const String8 & package)519     void setPackage(const String8& package) {
520         mPackage = package;
521         mSymbolsPrivatePackage = package;
522         mHavePrivateSymbols = false;
523     }
524 
525     const SortedVector<AaptGroupEntry>& getGroupEntries() const;
526 
527     virtual status_t addFile(const String8& name, const sp<AaptGroup>& file);
528 
529     sp<AaptFile> addFile(const String8& filePath,
530                          const AaptGroupEntry& entry,
531                          const String8& srcDir,
532                          sp<AaptGroup>* outGroup,
533                          const String8& resType);
534 
535     void addResource(const String8& leafName,
536                      const String8& path,
537                      const sp<AaptFile>& file,
538                      const String8& resType);
539 
addGroupEntry(const AaptGroupEntry & entry)540     void addGroupEntry(const AaptGroupEntry& entry) { mGroupEntries.add(entry); }
541 
542     ssize_t slurpFromArgs(Bundle* bundle);
543 
544     sp<AaptSymbols> getSymbolsFor(const String8& name);
545 
546     sp<AaptSymbols> getJavaSymbolsFor(const String8& name);
547 
548     status_t applyJavaSymbols();
549 
getSymbols()550     const DefaultKeyedVector<String8, sp<AaptSymbols> >& getSymbols() const { return mSymbols; }
551 
getSymbolsPrivatePackage()552     String8 getSymbolsPrivatePackage() const { return mSymbolsPrivatePackage; }
setSymbolsPrivatePackage(const String8 & pkg)553     void setSymbolsPrivatePackage(const String8& pkg) {
554         mSymbolsPrivatePackage = pkg;
555         mHavePrivateSymbols = mSymbolsPrivatePackage != mPackage;
556     }
557 
havePrivateSymbols()558     bool havePrivateSymbols() const { return mHavePrivateSymbols; }
559 
560     bool isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const;
561 
562     status_t buildIncludedResources(Bundle* bundle);
563     status_t addIncludedResources(const sp<AaptFile>& file);
564     const ResTable& getIncludedResources() const;
565     AssetManager& getAssetManager();
566 
567     void print(const String8& prefix) const;
568 
resDirs()569     inline const Vector<sp<AaptDir> >& resDirs() const { return mResDirs; }
570     sp<AaptDir> resDir(const String8& name) const;
571 
getOverlay()572     inline sp<AaptAssets> getOverlay() { return mOverlay; }
setOverlay(sp<AaptAssets> & overlay)573     inline void setOverlay(sp<AaptAssets>& overlay) { mOverlay = overlay; }
574 
getResources()575     inline KeyedVector<String8, sp<ResourceTypeSet> >* getResources() { return mRes; }
576     inline void
setResources(KeyedVector<String8,sp<ResourceTypeSet>> * res)577         setResources(KeyedVector<String8, sp<ResourceTypeSet> >* res) { delete mRes; mRes = res; }
578 
getFullResPaths()579     inline sp<FilePathStore>& getFullResPaths() { return mFullResPaths; }
580     inline void
setFullResPaths(sp<FilePathStore> & res)581         setFullResPaths(sp<FilePathStore>& res) { mFullResPaths = res; }
582 
getFullAssetPaths()583     inline sp<FilePathStore>& getFullAssetPaths() { return mFullAssetPaths; }
584     inline void
setFullAssetPaths(sp<FilePathStore> & res)585         setFullAssetPaths(sp<FilePathStore>& res) { mFullAssetPaths = res; }
586 
587 private:
588     virtual ssize_t slurpFullTree(Bundle* bundle,
589                                   const String8& srcDir,
590                                   const AaptGroupEntry& kind,
591                                   const String8& resType,
592                                   sp<FilePathStore>& fullResPaths,
593                                   const bool overwrite=false);
594 
595     ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir);
596     ssize_t slurpResourceZip(Bundle* bundle, const char* filename);
597 
598     status_t filter(Bundle* bundle);
599 
600     String8 mPackage;
601     SortedVector<AaptGroupEntry> mGroupEntries;
602     DefaultKeyedVector<String8, sp<AaptSymbols> > mSymbols;
603     DefaultKeyedVector<String8, sp<AaptSymbols> > mJavaSymbols;
604     String8 mSymbolsPrivatePackage;
605     bool mHavePrivateSymbols;
606 
607     Vector<sp<AaptDir> > mResDirs;
608 
609     bool mChanged;
610 
611     bool mHaveIncludedAssets;
612     AssetManager mIncludedAssets;
613 
614     sp<AaptAssets> mOverlay;
615     KeyedVector<String8, sp<ResourceTypeSet> >* mRes;
616 
617     sp<FilePathStore> mFullResPaths;
618     sp<FilePathStore> mFullAssetPaths;
619 };
620 
621 #endif // __AAPT_ASSETS_H
622 
623