blob: deb2bc52ab16549851094c8f828271004740c417 [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001//
2// Copyright 2006 The Android Open Source Project
3//
4// Android Asset Packaging Tool main entry point.
5//
Adam Lesinskifab50872014-04-16 14:40:42 -07006#include "ApkBuilder.h"
Adam Lesinski282e1812014-01-23 18:17:42 -08007#include "Main.h"
8#include "Bundle.h"
9#include "ResourceFilter.h"
10#include "ResourceTable.h"
11#include "Images.h"
12#include "XMLNode.h"
13
14#include <utils/Log.h>
15#include <utils/threads.h>
16#include <utils/List.h>
17#include <utils/Errors.h>
18
19#include <fcntl.h>
20#include <errno.h>
21
22using namespace android;
23
24/*
25 * Show version info. All the cool kids do it.
26 */
27int doVersion(Bundle* bundle)
28{
29 if (bundle->getFileSpecCount() != 0) {
30 printf("(ignoring extra arguments)\n");
31 }
32 printf("Android Asset Packaging Tool, v0.2\n");
33
34 return 0;
35}
36
37
38/*
39 * Open the file read only. The call fails if the file doesn't exist.
40 *
41 * Returns NULL on failure.
42 */
43ZipFile* openReadOnly(const char* fileName)
44{
45 ZipFile* zip;
46 status_t result;
47
48 zip = new ZipFile;
49 result = zip->open(fileName, ZipFile::kOpenReadOnly);
50 if (result != NO_ERROR) {
51 if (result == NAME_NOT_FOUND) {
52 fprintf(stderr, "ERROR: '%s' not found\n", fileName);
53 } else if (result == PERMISSION_DENIED) {
54 fprintf(stderr, "ERROR: '%s' access denied\n", fileName);
55 } else {
56 fprintf(stderr, "ERROR: failed opening '%s' as Zip file\n",
57 fileName);
58 }
59 delete zip;
60 return NULL;
61 }
62
63 return zip;
64}
65
66/*
67 * Open the file read-write. The file will be created if it doesn't
68 * already exist and "okayToCreate" is set.
69 *
70 * Returns NULL on failure.
71 */
72ZipFile* openReadWrite(const char* fileName, bool okayToCreate)
73{
74 ZipFile* zip = NULL;
75 status_t result;
76 int flags;
77
78 flags = ZipFile::kOpenReadWrite;
79 if (okayToCreate) {
80 flags |= ZipFile::kOpenCreate;
81 }
82
83 zip = new ZipFile;
84 result = zip->open(fileName, flags);
85 if (result != NO_ERROR) {
86 delete zip;
87 zip = NULL;
88 goto bail;
89 }
90
91bail:
92 return zip;
93}
94
95
96/*
97 * Return a short string describing the compression method.
98 */
99const char* compressionName(int method)
100{
101 if (method == ZipEntry::kCompressStored) {
102 return "Stored";
103 } else if (method == ZipEntry::kCompressDeflated) {
104 return "Deflated";
105 } else {
106 return "Unknown";
107 }
108}
109
110/*
111 * Return the percent reduction in size (0% == no compression).
112 */
113int calcPercent(long uncompressedLen, long compressedLen)
114{
115 if (!uncompressedLen) {
116 return 0;
117 } else {
118 return (int) (100.0 - (compressedLen * 100.0) / uncompressedLen + 0.5);
119 }
120}
121
122/*
123 * Handle the "list" command, which can be a simple file dump or
124 * a verbose listing.
125 *
126 * The verbose listing closely matches the output of the Info-ZIP "unzip"
127 * command.
128 */
129int doList(Bundle* bundle)
130{
131 int result = 1;
132 ZipFile* zip = NULL;
133 const ZipEntry* entry;
134 long totalUncLen, totalCompLen;
135 const char* zipFileName;
136
137 if (bundle->getFileSpecCount() != 1) {
138 fprintf(stderr, "ERROR: specify zip file name (only)\n");
139 goto bail;
140 }
141 zipFileName = bundle->getFileSpecEntry(0);
142
143 zip = openReadOnly(zipFileName);
144 if (zip == NULL) {
145 goto bail;
146 }
147
148 int count, i;
149
150 if (bundle->getVerbose()) {
151 printf("Archive: %s\n", zipFileName);
152 printf(
153 " Length Method Size Ratio Offset Date Time CRC-32 Name\n");
154 printf(
155 "-------- ------ ------- ----- ------- ---- ---- ------ ----\n");
156 }
157
158 totalUncLen = totalCompLen = 0;
159
160 count = zip->getNumEntries();
161 for (i = 0; i < count; i++) {
162 entry = zip->getEntryByIndex(i);
163 if (bundle->getVerbose()) {
164 char dateBuf[32];
165 time_t when;
166
167 when = entry->getModWhen();
168 strftime(dateBuf, sizeof(dateBuf), "%m-%d-%y %H:%M",
169 localtime(&when));
170
171 printf("%8ld %-7.7s %7ld %3d%% %8zd %s %08lx %s\n",
172 (long) entry->getUncompressedLen(),
173 compressionName(entry->getCompressionMethod()),
174 (long) entry->getCompressedLen(),
175 calcPercent(entry->getUncompressedLen(),
176 entry->getCompressedLen()),
177 (size_t) entry->getLFHOffset(),
178 dateBuf,
179 entry->getCRC32(),
180 entry->getFileName());
181 } else {
182 printf("%s\n", entry->getFileName());
183 }
184
185 totalUncLen += entry->getUncompressedLen();
186 totalCompLen += entry->getCompressedLen();
187 }
188
189 if (bundle->getVerbose()) {
190 printf(
191 "-------- ------- --- -------\n");
192 printf("%8ld %7ld %2d%% %d files\n",
193 totalUncLen,
194 totalCompLen,
195 calcPercent(totalUncLen, totalCompLen),
196 zip->getNumEntries());
197 }
198
199 if (bundle->getAndroidList()) {
200 AssetManager assets;
201 if (!assets.addAssetPath(String8(zipFileName), NULL)) {
202 fprintf(stderr, "ERROR: list -a failed because assets could not be loaded\n");
203 goto bail;
204 }
205
206 const ResTable& res = assets.getResources(false);
207 if (&res == NULL) {
208 printf("\nNo resource table found.\n");
209 } else {
210#ifndef HAVE_ANDROID_OS
211 printf("\nResource table:\n");
212 res.print(false);
213#endif
214 }
215
216 Asset* manifestAsset = assets.openNonAsset("AndroidManifest.xml",
217 Asset::ACCESS_BUFFER);
218 if (manifestAsset == NULL) {
219 printf("\nNo AndroidManifest.xml found.\n");
220 } else {
221 printf("\nAndroid manifest:\n");
222 ResXMLTree tree;
223 tree.setTo(manifestAsset->getBuffer(true),
224 manifestAsset->getLength());
225 printXMLBlock(&tree);
226 }
227 delete manifestAsset;
228 }
229
230 result = 0;
231
232bail:
233 delete zip;
234 return result;
235}
236
237static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes)
238{
239 size_t N = tree.getAttributeCount();
240 for (size_t i=0; i<N; i++) {
241 if (tree.getAttributeNameResID(i) == attrRes) {
242 return (ssize_t)i;
243 }
244 }
245 return -1;
246}
247
248String8 getAttribute(const ResXMLTree& tree, const char* ns,
249 const char* attr, String8* outError)
250{
251 ssize_t idx = tree.indexOfAttribute(ns, attr);
252 if (idx < 0) {
253 return String8();
254 }
255 Res_value value;
256 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
257 if (value.dataType != Res_value::TYPE_STRING) {
258 if (outError != NULL) {
259 *outError = "attribute is not a string value";
260 }
261 return String8();
262 }
263 }
264 size_t len;
265 const uint16_t* str = tree.getAttributeStringValue(idx, &len);
266 return str ? String8(str, len) : String8();
267}
268
269static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError)
270{
271 ssize_t idx = indexOfAttribute(tree, attrRes);
272 if (idx < 0) {
273 return String8();
274 }
275 Res_value value;
276 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
277 if (value.dataType != Res_value::TYPE_STRING) {
278 if (outError != NULL) {
279 *outError = "attribute is not a string value";
280 }
281 return String8();
282 }
283 }
284 size_t len;
285 const uint16_t* str = tree.getAttributeStringValue(idx, &len);
286 return str ? String8(str, len) : String8();
287}
288
289static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes,
290 String8* outError, int32_t defValue = -1)
291{
292 ssize_t idx = indexOfAttribute(tree, attrRes);
293 if (idx < 0) {
294 return defValue;
295 }
296 Res_value value;
297 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
298 if (value.dataType < Res_value::TYPE_FIRST_INT
299 || value.dataType > Res_value::TYPE_LAST_INT) {
300 if (outError != NULL) {
301 *outError = "attribute is not an integer value";
302 }
303 return defValue;
304 }
305 }
306 return value.data;
307}
308
309static int32_t getResolvedIntegerAttribute(const ResTable* resTable, const ResXMLTree& tree,
310 uint32_t attrRes, String8* outError, int32_t defValue = -1)
311{
312 ssize_t idx = indexOfAttribute(tree, attrRes);
313 if (idx < 0) {
314 return defValue;
315 }
316 Res_value value;
317 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
318 if (value.dataType == Res_value::TYPE_REFERENCE) {
319 resTable->resolveReference(&value, 0);
320 }
321 if (value.dataType < Res_value::TYPE_FIRST_INT
322 || value.dataType > Res_value::TYPE_LAST_INT) {
323 if (outError != NULL) {
324 *outError = "attribute is not an integer value";
325 }
326 return defValue;
327 }
328 }
329 return value.data;
330}
331
332static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree,
333 uint32_t attrRes, String8* outError)
334{
335 ssize_t idx = indexOfAttribute(tree, attrRes);
336 if (idx < 0) {
337 return String8();
338 }
339 Res_value value;
340 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
341 if (value.dataType == Res_value::TYPE_STRING) {
342 size_t len;
343 const uint16_t* str = tree.getAttributeStringValue(idx, &len);
344 return str ? String8(str, len) : String8();
345 }
346 resTable->resolveReference(&value, 0);
347 if (value.dataType != Res_value::TYPE_STRING) {
348 if (outError != NULL) {
349 *outError = "attribute is not a string value";
350 }
351 return String8();
352 }
353 }
354 size_t len;
355 const Res_value* value2 = &value;
356 const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len);
357 return str ? String8(str, len) : String8();
358}
359
360static void getResolvedResourceAttribute(Res_value* value, const ResTable* resTable,
361 const ResXMLTree& tree, uint32_t attrRes, String8* outError)
362{
363 ssize_t idx = indexOfAttribute(tree, attrRes);
364 if (idx < 0) {
365 if (outError != NULL) {
366 *outError = "attribute could not be found";
367 }
368 return;
369 }
370 if (tree.getAttributeValue(idx, value) != NO_ERROR) {
371 if (value->dataType == Res_value::TYPE_REFERENCE) {
372 resTable->resolveReference(value, 0);
373 }
374 // The attribute was found and was resolved if need be.
375 return;
376 }
377 if (outError != NULL) {
378 *outError = "error getting resolved resource attribute";
379 }
380}
381
Maurice Chu76327312013-10-16 18:28:46 -0700382static void printResolvedResourceAttribute(const ResTable* resTable, const ResXMLTree& tree,
383 uint32_t attrRes, String8 attrLabel, String8* outError)
384{
385 Res_value value;
386 getResolvedResourceAttribute(&value, resTable, tree, attrRes, outError);
387 if (*outError != "") {
388 *outError = "error print resolved resource attribute";
389 return;
390 }
391 if (value.dataType == Res_value::TYPE_STRING) {
392 String8 result = getResolvedAttribute(resTable, tree, attrRes, outError);
Maurice Chu2675f762013-10-22 17:33:11 -0700393 printf("%s='%s'", attrLabel.string(),
394 ResTable::normalizeForOutput(result.string()).string());
Maurice Chu76327312013-10-16 18:28:46 -0700395 } else if (Res_value::TYPE_FIRST_INT <= value.dataType &&
396 value.dataType <= Res_value::TYPE_LAST_INT) {
397 printf("%s='%d'", attrLabel.string(), value.data);
398 } else {
399 printf("%s='0x%x'", attrLabel.string(), (int)value.data);
400 }
401}
402
Adam Lesinski282e1812014-01-23 18:17:42 -0800403// These are attribute resource constants for the platform, as found
404// in android.R.attr
405enum {
406 LABEL_ATTR = 0x01010001,
407 ICON_ATTR = 0x01010002,
408 NAME_ATTR = 0x01010003,
Adam Lesinskia5018c92013-09-30 16:23:15 -0700409 PERMISSION_ATTR = 0x01010006,
Adam Lesinski94fc9122013-09-30 17:16:09 -0700410 RESOURCE_ATTR = 0x01010025,
Adam Lesinski282e1812014-01-23 18:17:42 -0800411 DEBUGGABLE_ATTR = 0x0101000f,
412 VALUE_ATTR = 0x01010024,
413 VERSION_CODE_ATTR = 0x0101021b,
414 VERSION_NAME_ATTR = 0x0101021c,
415 SCREEN_ORIENTATION_ATTR = 0x0101001e,
416 MIN_SDK_VERSION_ATTR = 0x0101020c,
417 MAX_SDK_VERSION_ATTR = 0x01010271,
418 REQ_TOUCH_SCREEN_ATTR = 0x01010227,
419 REQ_KEYBOARD_TYPE_ATTR = 0x01010228,
420 REQ_HARD_KEYBOARD_ATTR = 0x01010229,
421 REQ_NAVIGATION_ATTR = 0x0101022a,
422 REQ_FIVE_WAY_NAV_ATTR = 0x01010232,
423 TARGET_SDK_VERSION_ATTR = 0x01010270,
424 TEST_ONLY_ATTR = 0x01010272,
425 ANY_DENSITY_ATTR = 0x0101026c,
426 GL_ES_VERSION_ATTR = 0x01010281,
427 SMALL_SCREEN_ATTR = 0x01010284,
428 NORMAL_SCREEN_ATTR = 0x01010285,
429 LARGE_SCREEN_ATTR = 0x01010286,
430 XLARGE_SCREEN_ATTR = 0x010102bf,
431 REQUIRED_ATTR = 0x0101028e,
432 SCREEN_SIZE_ATTR = 0x010102ca,
433 SCREEN_DENSITY_ATTR = 0x010102cb,
434 REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364,
435 COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365,
436 LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366,
437 PUBLIC_KEY_ATTR = 0x010103a6,
Adam Lesinski94fc9122013-09-30 17:16:09 -0700438 CATEGORY_ATTR = 0x010103e8,
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800439 BANNER_ATTR = 0x10103f2,
Adam Lesinski282e1812014-01-23 18:17:42 -0800440};
441
Maurice Chu2675f762013-10-22 17:33:11 -0700442String8 getComponentName(String8 &pkgName, String8 &componentName) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800443 ssize_t idx = componentName.find(".");
444 String8 retStr(pkgName);
445 if (idx == 0) {
446 retStr += componentName;
447 } else if (idx < 0) {
448 retStr += ".";
449 retStr += componentName;
450 } else {
Maurice Chu2675f762013-10-22 17:33:11 -0700451 return componentName;
Adam Lesinski282e1812014-01-23 18:17:42 -0800452 }
Maurice Chu2675f762013-10-22 17:33:11 -0700453 return retStr;
Adam Lesinski282e1812014-01-23 18:17:42 -0800454}
455
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700456static void printCompatibleScreens(ResXMLTree& tree, String8* outError) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800457 size_t len;
458 ResXMLTree::event_code_t code;
459 int depth = 0;
460 bool first = true;
461 printf("compatible-screens:");
462 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
463 if (code == ResXMLTree::END_TAG) {
464 depth--;
465 if (depth < 0) {
466 break;
467 }
468 continue;
469 }
470 if (code != ResXMLTree::START_TAG) {
471 continue;
472 }
473 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700474 const char16_t* ctag16 = tree.getElementName(&len);
475 if (ctag16 == NULL) {
476 *outError = "failed to get XML element name (bad string pool)";
477 return;
478 }
479 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -0800480 if (tag == "screen") {
481 int32_t screenSize = getIntegerAttribute(tree,
482 SCREEN_SIZE_ATTR, NULL, -1);
483 int32_t screenDensity = getIntegerAttribute(tree,
484 SCREEN_DENSITY_ATTR, NULL, -1);
485 if (screenSize > 0 && screenDensity > 0) {
486 if (!first) {
487 printf(",");
488 }
489 first = false;
490 printf("'%d/%d'", screenSize, screenDensity);
491 }
492 }
493 }
494 printf("\n");
495}
496
Adam Lesinski58f1f362013-11-12 12:59:08 -0800497static void printUsesPermission(const String8& name, bool optional=false, int maxSdkVersion=-1) {
498 printf("uses-permission: name='%s'", ResTable::normalizeForOutput(name.string()).string());
499 if (maxSdkVersion != -1) {
500 printf(" maxSdkVersion='%d'", maxSdkVersion);
501 }
502 printf("\n");
503
504 if (optional) {
505 printf("optional-permission: name='%s'",
506 ResTable::normalizeForOutput(name.string()).string());
507 if (maxSdkVersion != -1) {
508 printf(" maxSdkVersion='%d'", maxSdkVersion);
509 }
510 printf("\n");
511 }
512}
513
514static void printUsesImpliedPermission(const String8& name, const String8& reason) {
515 printf("uses-implied-permission: name='%s' reason='%s'\n",
516 ResTable::normalizeForOutput(name.string()).string(),
517 ResTable::normalizeForOutput(reason.string()).string());
518}
519
Adam Lesinski94fc9122013-09-30 17:16:09 -0700520Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool offHost,
521 String8 *outError = NULL)
522{
523 Asset* aidAsset = assets.openNonAsset(xmlPath, Asset::ACCESS_BUFFER);
524 if (aidAsset == NULL) {
525 if (outError != NULL) *outError = "xml resource does not exist";
526 return Vector<String8>();
527 }
528
529 const String8 serviceTagName(offHost ? "offhost-apdu-service" : "host-apdu-service");
530
531 bool withinApduService = false;
532 Vector<String8> categories;
533
534 String8 error;
535 ResXMLTree tree;
536 tree.setTo(aidAsset->getBuffer(true), aidAsset->getLength());
537
538 size_t len;
539 int depth = 0;
540 ResXMLTree::event_code_t code;
541 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
542 if (code == ResXMLTree::END_TAG) {
543 depth--;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700544 const char16_t* ctag16 = tree.getElementName(&len);
545 if (ctag16 == NULL) {
546 *outError = "failed to get XML element name (bad string pool)";
547 return Vector<String8>();
548 }
549 String8 tag(ctag16);
Adam Lesinski94fc9122013-09-30 17:16:09 -0700550
551 if (depth == 0 && tag == serviceTagName) {
552 withinApduService = false;
553 }
554
555 } else if (code == ResXMLTree::START_TAG) {
556 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700557 const char16_t* ctag16 = tree.getElementName(&len);
558 if (ctag16 == NULL) {
559 *outError = "failed to get XML element name (bad string pool)";
560 return Vector<String8>();
561 }
562 String8 tag(ctag16);
Adam Lesinski94fc9122013-09-30 17:16:09 -0700563
564 if (depth == 1) {
565 if (tag == serviceTagName) {
566 withinApduService = true;
567 }
568 } else if (depth == 2 && withinApduService) {
569 if (tag == "aid-group") {
570 String8 category = getAttribute(tree, CATEGORY_ATTR, &error);
571 if (error != "") {
572 if (outError != NULL) *outError = error;
573 return Vector<String8>();
574 }
575
576 categories.add(category);
577 }
578 }
579 }
580 }
581 aidAsset->close();
582 return categories;
583}
584
Adam Lesinski282e1812014-01-23 18:17:42 -0800585/*
586 * Handle the "dump" command, to extract select data from an archive.
587 */
588extern char CONSOLE_DATA[2925]; // see EOF
589int doDump(Bundle* bundle)
590{
591 status_t result = UNKNOWN_ERROR;
592 Asset* asset = NULL;
593
594 if (bundle->getFileSpecCount() < 1) {
595 fprintf(stderr, "ERROR: no dump option specified\n");
596 return 1;
597 }
598
599 if (bundle->getFileSpecCount() < 2) {
600 fprintf(stderr, "ERROR: no dump file specified\n");
601 return 1;
602 }
603
604 const char* option = bundle->getFileSpecEntry(0);
605 const char* filename = bundle->getFileSpecEntry(1);
606
607 AssetManager assets;
Narayan Kamathf85e41f2014-01-24 14:14:30 +0000608 int32_t assetsCookie;
Adam Lesinski282e1812014-01-23 18:17:42 -0800609 if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
610 fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
611 return 1;
612 }
613
614 // Make a dummy config for retrieving resources... we need to supply
615 // non-default values for some configs so that we can retrieve resources
616 // in the app that don't have a default. The most important of these is
617 // the API version because key resources like icons will have an implicit
618 // version if they are using newer config types like density.
619 ResTable_config config;
Narayan Kamath91447d82014-01-21 15:32:36 +0000620 memset(&config, 0, sizeof(ResTable_config));
Adam Lesinski282e1812014-01-23 18:17:42 -0800621 config.language[0] = 'e';
622 config.language[1] = 'n';
623 config.country[0] = 'U';
624 config.country[1] = 'S';
625 config.orientation = ResTable_config::ORIENTATION_PORT;
626 config.density = ResTable_config::DENSITY_MEDIUM;
627 config.sdkVersion = 10000; // Very high.
628 config.screenWidthDp = 320;
629 config.screenHeightDp = 480;
630 config.smallestScreenWidthDp = 320;
631 assets.setConfiguration(config);
632
633 const ResTable& res = assets.getResources(false);
634 if (&res == NULL) {
635 fprintf(stderr, "ERROR: dump failed because no resource table was found\n");
636 goto bail;
637 }
638
639 if (strcmp("resources", option) == 0) {
640#ifndef HAVE_ANDROID_OS
641 res.print(bundle->getValues());
642#endif
643
644 } else if (strcmp("strings", option) == 0) {
645 const ResStringPool* pool = res.getTableStringBlock(0);
646 printStringPool(pool);
647
648 } else if (strcmp("xmltree", option) == 0) {
649 if (bundle->getFileSpecCount() < 3) {
650 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
651 goto bail;
652 }
653
654 for (int i=2; i<bundle->getFileSpecCount(); i++) {
655 const char* resname = bundle->getFileSpecEntry(i);
656 ResXMLTree tree;
657 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
658 if (asset == NULL) {
659 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
660 goto bail;
661 }
662
663 if (tree.setTo(asset->getBuffer(true),
664 asset->getLength()) != NO_ERROR) {
665 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
666 goto bail;
667 }
668 tree.restart();
669 printXMLBlock(&tree);
670 tree.uninit();
671 delete asset;
672 asset = NULL;
673 }
674
675 } else if (strcmp("xmlstrings", option) == 0) {
676 if (bundle->getFileSpecCount() < 3) {
677 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
678 goto bail;
679 }
680
681 for (int i=2; i<bundle->getFileSpecCount(); i++) {
682 const char* resname = bundle->getFileSpecEntry(i);
683 ResXMLTree tree;
684 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
685 if (asset == NULL) {
686 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
687 goto bail;
688 }
689
690 if (tree.setTo(asset->getBuffer(true),
691 asset->getLength()) != NO_ERROR) {
692 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
693 goto bail;
694 }
695 printStringPool(&tree.getStrings());
696 delete asset;
697 asset = NULL;
698 }
699
700 } else {
701 ResXMLTree tree;
702 asset = assets.openNonAsset("AndroidManifest.xml",
703 Asset::ACCESS_BUFFER);
704 if (asset == NULL) {
705 fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
706 goto bail;
707 }
708
709 if (tree.setTo(asset->getBuffer(true),
710 asset->getLength()) != NO_ERROR) {
711 fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
712 goto bail;
713 }
714 tree.restart();
715
716 if (strcmp("permissions", option) == 0) {
717 size_t len;
718 ResXMLTree::event_code_t code;
719 int depth = 0;
720 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
721 if (code == ResXMLTree::END_TAG) {
722 depth--;
723 continue;
724 }
725 if (code != ResXMLTree::START_TAG) {
726 continue;
727 }
728 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700729 const char16_t* ctag16 = tree.getElementName(&len);
730 if (ctag16 == NULL) {
731 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
732 goto bail;
733 }
734 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -0800735 //printf("Depth %d tag %s\n", depth, tag.string());
736 if (depth == 1) {
737 if (tag != "manifest") {
738 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
739 goto bail;
740 }
741 String8 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -0700742 printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800743 } else if (depth == 2 && tag == "permission") {
744 String8 error;
745 String8 name = getAttribute(tree, NAME_ATTR, &error);
746 if (error != "") {
747 fprintf(stderr, "ERROR: %s\n", error.string());
748 goto bail;
749 }
Maurice Chu2675f762013-10-22 17:33:11 -0700750 printf("permission: %s\n",
751 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800752 } else if (depth == 2 && tag == "uses-permission") {
753 String8 error;
754 String8 name = getAttribute(tree, NAME_ATTR, &error);
755 if (error != "") {
756 fprintf(stderr, "ERROR: %s\n", error.string());
757 goto bail;
758 }
Adam Lesinski58f1f362013-11-12 12:59:08 -0800759 printUsesPermission(name,
760 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
761 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
Adam Lesinski282e1812014-01-23 18:17:42 -0800762 }
763 }
764 } else if (strcmp("badging", option) == 0) {
765 Vector<String8> locales;
766 res.getLocales(&locales);
767
768 Vector<ResTable_config> configs;
769 res.getConfigurations(&configs);
770 SortedVector<int> densities;
771 const size_t NC = configs.size();
772 for (size_t i=0; i<NC; i++) {
773 int dens = configs[i].density;
774 if (dens == 0) {
775 dens = 160;
776 }
777 densities.add(dens);
778 }
779
780 size_t len;
781 ResXMLTree::event_code_t code;
782 int depth = 0;
783 String8 error;
784 bool withinActivity = false;
785 bool isMainActivity = false;
786 bool isLauncherActivity = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800787 bool isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800788 bool isSearchable = false;
789 bool withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700790 bool withinSupportsInput = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800791 bool withinReceiver = false;
792 bool withinService = false;
793 bool withinIntentFilter = false;
794 bool hasMainActivity = false;
795 bool hasOtherActivities = false;
796 bool hasOtherReceivers = false;
797 bool hasOtherServices = false;
798 bool hasWallpaperService = false;
799 bool hasImeService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700800 bool hasAccessibilityService = false;
801 bool hasPrintService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800802 bool hasWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700803 bool hasDeviceAdminReceiver = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800804 bool hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700805 bool hasPaymentService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800806 bool actMainActivity = false;
807 bool actWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700808 bool actDeviceAdminEnabled = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800809 bool actImeService = false;
810 bool actWallpaperService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700811 bool actAccessibilityService = false;
812 bool actPrintService = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700813 bool actHostApduService = false;
814 bool actOffHostApduService = false;
815 bool hasMetaHostPaymentCategory = false;
816 bool hasMetaOffHostPaymentCategory = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700817
818 // These permissions are required by services implementing services
819 // the system binds to (IME, Accessibility, PrintServices, etc.)
820 bool hasBindDeviceAdminPermission = false;
821 bool hasBindInputMethodPermission = false;
822 bool hasBindAccessibilityServicePermission = false;
823 bool hasBindPrintServicePermission = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700824 bool hasBindNfcServicePermission = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800825
826 // These two implement the implicit permissions that are granted
827 // to pre-1.6 applications.
828 bool hasWriteExternalStoragePermission = false;
829 bool hasReadPhoneStatePermission = false;
830
831 // If an app requests write storage, they will also get read storage.
832 bool hasReadExternalStoragePermission = false;
833
834 // Implement transition to read and write call log.
835 bool hasReadContactsPermission = false;
836 bool hasWriteContactsPermission = false;
837 bool hasReadCallLogPermission = false;
838 bool hasWriteCallLogPermission = false;
839
840 // This next group of variables is used to implement a group of
841 // backward-compatibility heuristics necessitated by the addition of
842 // some new uses-feature constants in 2.1 and 2.2. In most cases, the
843 // heuristic is "if an app requests a permission but doesn't explicitly
844 // request the corresponding <uses-feature>, presume it's there anyway".
845 bool specCameraFeature = false; // camera-related
846 bool specCameraAutofocusFeature = false;
847 bool reqCameraAutofocusFeature = false;
848 bool reqCameraFlashFeature = false;
849 bool hasCameraPermission = false;
850 bool specLocationFeature = false; // location-related
851 bool specNetworkLocFeature = false;
852 bool reqNetworkLocFeature = false;
853 bool specGpsFeature = false;
854 bool reqGpsFeature = false;
855 bool hasMockLocPermission = false;
856 bool hasCoarseLocPermission = false;
857 bool hasGpsPermission = false;
858 bool hasGeneralLocPermission = false;
859 bool specBluetoothFeature = false; // Bluetooth API-related
860 bool hasBluetoothPermission = false;
861 bool specMicrophoneFeature = false; // microphone-related
862 bool hasRecordAudioPermission = false;
863 bool specWiFiFeature = false;
864 bool hasWiFiPermission = false;
865 bool specTelephonyFeature = false; // telephony-related
866 bool reqTelephonySubFeature = false;
867 bool hasTelephonyPermission = false;
868 bool specTouchscreenFeature = false; // touchscreen-related
869 bool specMultitouchFeature = false;
870 bool reqDistinctMultitouchFeature = false;
871 bool specScreenPortraitFeature = false;
872 bool specScreenLandscapeFeature = false;
873 bool reqScreenPortraitFeature = false;
874 bool reqScreenLandscapeFeature = false;
875 // 2.2 also added some other features that apps can request, but that
876 // have no corresponding permission, so we cannot implement any
877 // back-compatibility heuristic for them. The below are thus unnecessary
878 // (but are retained here for documentary purposes.)
879 //bool specCompassFeature = false;
880 //bool specAccelerometerFeature = false;
881 //bool specProximityFeature = false;
882 //bool specAmbientLightFeature = false;
883 //bool specLiveWallpaperFeature = false;
884
885 int targetSdk = 0;
886 int smallScreen = 1;
887 int normalScreen = 1;
888 int largeScreen = 1;
889 int xlargeScreen = 1;
890 int anyDensity = 1;
891 int requiresSmallestWidthDp = 0;
892 int compatibleWidthLimitDp = 0;
893 int largestWidthLimitDp = 0;
894 String8 pkg;
895 String8 activityName;
896 String8 activityLabel;
897 String8 activityIcon;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800898 String8 activityBanner;
Adam Lesinski282e1812014-01-23 18:17:42 -0800899 String8 receiverName;
900 String8 serviceName;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700901 Vector<String8> supportedInput;
Adam Lesinski282e1812014-01-23 18:17:42 -0800902 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
903 if (code == ResXMLTree::END_TAG) {
904 depth--;
905 if (depth < 2) {
Michael Wrightec4fdec2013-09-06 16:50:52 -0700906 if (withinSupportsInput && !supportedInput.isEmpty()) {
907 printf("supports-input: '");
908 const size_t N = supportedInput.size();
909 for (size_t i=0; i<N; i++) {
Maurice Chu2675f762013-10-22 17:33:11 -0700910 printf("%s", ResTable::normalizeForOutput(
911 supportedInput[i].string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -0700912 if (i != N - 1) {
913 printf("' '");
914 } else {
915 printf("'\n");
916 }
917 }
918 supportedInput.clear();
919 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800920 withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700921 withinSupportsInput = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800922 } else if (depth < 3) {
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800923 if (withinActivity && isMainActivity) {
Maurice Chu2675f762013-10-22 17:33:11 -0700924 String8 aName(getComponentName(pkg, activityName));
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800925 if (isLauncherActivity) {
926 printf("launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -0800927 if (aName.length() > 0) {
928 printf(" name='%s' ",
929 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800930 }
931 printf(" label='%s' icon='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -0800932 ResTable::normalizeForOutput(activityLabel.string()).string(),
933 ResTable::normalizeForOutput(activityIcon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800934 }
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800935 if (isLeanbackLauncherActivity) {
936 printf("leanback-launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -0800937 if (aName.length() > 0) {
938 printf(" name='%s' ",
939 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800940 }
941 printf(" label='%s' icon='%s' banner='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -0800942 ResTable::normalizeForOutput(activityLabel.string()).string(),
943 ResTable::normalizeForOutput(activityIcon.string()).string(),
944 ResTable::normalizeForOutput(activityBanner.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800945 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800946 }
947 if (!hasIntentFilter) {
948 hasOtherActivities |= withinActivity;
949 hasOtherReceivers |= withinReceiver;
950 hasOtherServices |= withinService;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700951 } else {
952 if (withinService) {
953 hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
954 hasBindNfcServicePermission);
955 hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
956 hasBindNfcServicePermission);
957 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800958 }
959 withinActivity = false;
960 withinService = false;
961 withinReceiver = false;
962 hasIntentFilter = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800963 isMainActivity = isLauncherActivity = isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800964 } else if (depth < 4) {
965 if (withinIntentFilter) {
966 if (withinActivity) {
967 hasMainActivity |= actMainActivity;
968 hasOtherActivities |= !actMainActivity;
969 } else if (withinReceiver) {
970 hasWidgetReceivers |= actWidgetReceivers;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700971 hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
972 hasBindDeviceAdminPermission);
973 hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
Adam Lesinski282e1812014-01-23 18:17:42 -0800974 } else if (withinService) {
975 hasImeService |= actImeService;
976 hasWallpaperService |= actWallpaperService;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700977 hasAccessibilityService |= (actAccessibilityService &&
978 hasBindAccessibilityServicePermission);
979 hasPrintService |= (actPrintService && hasBindPrintServicePermission);
980 hasOtherServices |= (!actImeService && !actWallpaperService &&
Adam Lesinski94fc9122013-09-30 17:16:09 -0700981 !actAccessibilityService && !actPrintService &&
982 !actHostApduService && !actOffHostApduService);
Adam Lesinski282e1812014-01-23 18:17:42 -0800983 }
984 }
985 withinIntentFilter = false;
986 }
987 continue;
988 }
989 if (code != ResXMLTree::START_TAG) {
990 continue;
991 }
992 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700993
994 const char16_t* ctag16 = tree.getElementName(&len);
995 if (ctag16 == NULL) {
996 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
997 goto bail;
998 }
999 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -08001000 //printf("Depth %d, %s\n", depth, tag.string());
1001 if (depth == 1) {
1002 if (tag != "manifest") {
1003 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
1004 goto bail;
1005 }
1006 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -07001007 printf("package: name='%s' ",
1008 ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001009 int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
1010 if (error != "") {
1011 fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
1012 goto bail;
1013 }
1014 if (versionCode > 0) {
1015 printf("versionCode='%d' ", versionCode);
1016 } else {
1017 printf("versionCode='' ");
1018 }
1019 String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
1020 if (error != "") {
1021 fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
1022 goto bail;
1023 }
Maurice Chu2675f762013-10-22 17:33:11 -07001024 printf("versionName='%s'\n",
1025 ResTable::normalizeForOutput(versionName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001026 } else if (depth == 2) {
1027 withinApplication = false;
1028 if (tag == "application") {
1029 withinApplication = true;
1030
1031 String8 label;
1032 const size_t NL = locales.size();
1033 for (size_t i=0; i<NL; i++) {
1034 const char* localeStr = locales[i].string();
1035 assets.setLocale(localeStr != NULL ? localeStr : "");
1036 String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1037 if (llabel != "") {
1038 if (localeStr == NULL || strlen(localeStr) == 0) {
1039 label = llabel;
Maurice Chu2675f762013-10-22 17:33:11 -07001040 printf("application-label:'%s'\n",
1041 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001042 } else {
1043 if (label == "") {
1044 label = llabel;
1045 }
1046 printf("application-label-%s:'%s'\n", localeStr,
Maurice Chu2675f762013-10-22 17:33:11 -07001047 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001048 }
1049 }
1050 }
1051
1052 ResTable_config tmpConfig = config;
1053 const size_t ND = densities.size();
1054 for (size_t i=0; i<ND; i++) {
1055 tmpConfig.density = densities[i];
1056 assets.setConfiguration(tmpConfig);
1057 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1058 if (icon != "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001059 printf("application-icon-%d:'%s'\n", densities[i],
1060 ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001061 }
1062 }
1063 assets.setConfiguration(config);
1064
1065 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1066 if (error != "") {
1067 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
1068 goto bail;
1069 }
1070 int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
1071 if (error != "") {
1072 fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
1073 goto bail;
1074 }
Maurice Chu2675f762013-10-22 17:33:11 -07001075 printf("application: label='%s' ",
1076 ResTable::normalizeForOutput(label.string()).string());
1077 printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001078 if (testOnly != 0) {
1079 printf("testOnly='%d'\n", testOnly);
1080 }
1081
1082 int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
1083 if (error != "") {
1084 fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
1085 goto bail;
1086 }
1087 if (debuggable != 0) {
1088 printf("application-debuggable\n");
1089 }
1090 } else if (tag == "uses-sdk") {
1091 int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
1092 if (error != "") {
1093 error = "";
1094 String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
1095 if (error != "") {
1096 fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
1097 error.string());
1098 goto bail;
1099 }
1100 if (name == "Donut") targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001101 printf("sdkVersion:'%s'\n",
1102 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001103 } else if (code != -1) {
1104 targetSdk = code;
1105 printf("sdkVersion:'%d'\n", code);
1106 }
1107 code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
1108 if (code != -1) {
1109 printf("maxSdkVersion:'%d'\n", code);
1110 }
1111 code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
1112 if (error != "") {
1113 error = "";
1114 String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
1115 if (error != "") {
1116 fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
1117 error.string());
1118 goto bail;
1119 }
1120 if (name == "Donut" && targetSdk < 4) targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001121 printf("targetSdkVersion:'%s'\n",
1122 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001123 } else if (code != -1) {
1124 if (targetSdk < code) {
1125 targetSdk = code;
1126 }
1127 printf("targetSdkVersion:'%d'\n", code);
1128 }
1129 } else if (tag == "uses-configuration") {
1130 int32_t reqTouchScreen = getIntegerAttribute(tree,
1131 REQ_TOUCH_SCREEN_ATTR, NULL, 0);
1132 int32_t reqKeyboardType = getIntegerAttribute(tree,
1133 REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
1134 int32_t reqHardKeyboard = getIntegerAttribute(tree,
1135 REQ_HARD_KEYBOARD_ATTR, NULL, 0);
1136 int32_t reqNavigation = getIntegerAttribute(tree,
1137 REQ_NAVIGATION_ATTR, NULL, 0);
1138 int32_t reqFiveWayNav = getIntegerAttribute(tree,
1139 REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
1140 printf("uses-configuration:");
1141 if (reqTouchScreen != 0) {
1142 printf(" reqTouchScreen='%d'", reqTouchScreen);
1143 }
1144 if (reqKeyboardType != 0) {
1145 printf(" reqKeyboardType='%d'", reqKeyboardType);
1146 }
1147 if (reqHardKeyboard != 0) {
1148 printf(" reqHardKeyboard='%d'", reqHardKeyboard);
1149 }
1150 if (reqNavigation != 0) {
1151 printf(" reqNavigation='%d'", reqNavigation);
1152 }
1153 if (reqFiveWayNav != 0) {
1154 printf(" reqFiveWayNav='%d'", reqFiveWayNav);
1155 }
1156 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001157 } else if (tag == "supports-input") {
1158 withinSupportsInput = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001159 } else if (tag == "supports-screens") {
1160 smallScreen = getIntegerAttribute(tree,
1161 SMALL_SCREEN_ATTR, NULL, 1);
1162 normalScreen = getIntegerAttribute(tree,
1163 NORMAL_SCREEN_ATTR, NULL, 1);
1164 largeScreen = getIntegerAttribute(tree,
1165 LARGE_SCREEN_ATTR, NULL, 1);
1166 xlargeScreen = getIntegerAttribute(tree,
1167 XLARGE_SCREEN_ATTR, NULL, 1);
1168 anyDensity = getIntegerAttribute(tree,
1169 ANY_DENSITY_ATTR, NULL, 1);
1170 requiresSmallestWidthDp = getIntegerAttribute(tree,
1171 REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
1172 compatibleWidthLimitDp = getIntegerAttribute(tree,
1173 COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1174 largestWidthLimitDp = getIntegerAttribute(tree,
1175 LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1176 } else if (tag == "uses-feature") {
1177 String8 name = getAttribute(tree, NAME_ATTR, &error);
1178
1179 if (name != "" && error == "") {
1180 int req = getIntegerAttribute(tree,
1181 REQUIRED_ATTR, NULL, 1);
1182
1183 if (name == "android.hardware.camera") {
1184 specCameraFeature = true;
1185 } else if (name == "android.hardware.camera.autofocus") {
1186 // these have no corresponding permission to check for,
1187 // but should imply the foundational camera permission
1188 reqCameraAutofocusFeature = reqCameraAutofocusFeature || req;
1189 specCameraAutofocusFeature = true;
1190 } else if (req && (name == "android.hardware.camera.flash")) {
1191 // these have no corresponding permission to check for,
1192 // but should imply the foundational camera permission
1193 reqCameraFlashFeature = true;
1194 } else if (name == "android.hardware.location") {
1195 specLocationFeature = true;
1196 } else if (name == "android.hardware.location.network") {
1197 specNetworkLocFeature = true;
1198 reqNetworkLocFeature = reqNetworkLocFeature || req;
1199 } else if (name == "android.hardware.location.gps") {
1200 specGpsFeature = true;
1201 reqGpsFeature = reqGpsFeature || req;
1202 } else if (name == "android.hardware.bluetooth") {
1203 specBluetoothFeature = true;
1204 } else if (name == "android.hardware.touchscreen") {
1205 specTouchscreenFeature = true;
1206 } else if (name == "android.hardware.touchscreen.multitouch") {
1207 specMultitouchFeature = true;
1208 } else if (name == "android.hardware.touchscreen.multitouch.distinct") {
1209 reqDistinctMultitouchFeature = reqDistinctMultitouchFeature || req;
1210 } else if (name == "android.hardware.microphone") {
1211 specMicrophoneFeature = true;
1212 } else if (name == "android.hardware.wifi") {
1213 specWiFiFeature = true;
1214 } else if (name == "android.hardware.telephony") {
1215 specTelephonyFeature = true;
1216 } else if (req && (name == "android.hardware.telephony.gsm" ||
1217 name == "android.hardware.telephony.cdma")) {
1218 // these have no corresponding permission to check for,
1219 // but should imply the foundational telephony permission
1220 reqTelephonySubFeature = true;
1221 } else if (name == "android.hardware.screen.portrait") {
1222 specScreenPortraitFeature = true;
1223 } else if (name == "android.hardware.screen.landscape") {
1224 specScreenLandscapeFeature = true;
1225 }
1226 printf("uses-feature%s:'%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001227 req ? "" : "-not-required",
1228 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001229 } else {
1230 int vers = getIntegerAttribute(tree,
1231 GL_ES_VERSION_ATTR, &error);
1232 if (error == "") {
1233 printf("uses-gl-es:'0x%x'\n", vers);
1234 }
1235 }
1236 } else if (tag == "uses-permission") {
1237 String8 name = getAttribute(tree, NAME_ATTR, &error);
1238 if (name != "" && error == "") {
1239 if (name == "android.permission.CAMERA") {
1240 hasCameraPermission = true;
1241 } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
1242 hasGpsPermission = true;
1243 } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
1244 hasMockLocPermission = true;
1245 } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
1246 hasCoarseLocPermission = true;
1247 } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
1248 name == "android.permission.INSTALL_LOCATION_PROVIDER") {
1249 hasGeneralLocPermission = true;
1250 } else if (name == "android.permission.BLUETOOTH" ||
1251 name == "android.permission.BLUETOOTH_ADMIN") {
1252 hasBluetoothPermission = true;
1253 } else if (name == "android.permission.RECORD_AUDIO") {
1254 hasRecordAudioPermission = true;
1255 } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
1256 name == "android.permission.CHANGE_WIFI_STATE" ||
1257 name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
1258 hasWiFiPermission = true;
1259 } else if (name == "android.permission.CALL_PHONE" ||
1260 name == "android.permission.CALL_PRIVILEGED" ||
1261 name == "android.permission.MODIFY_PHONE_STATE" ||
1262 name == "android.permission.PROCESS_OUTGOING_CALLS" ||
1263 name == "android.permission.READ_SMS" ||
1264 name == "android.permission.RECEIVE_SMS" ||
1265 name == "android.permission.RECEIVE_MMS" ||
1266 name == "android.permission.RECEIVE_WAP_PUSH" ||
1267 name == "android.permission.SEND_SMS" ||
1268 name == "android.permission.WRITE_APN_SETTINGS" ||
1269 name == "android.permission.WRITE_SMS") {
1270 hasTelephonyPermission = true;
1271 } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
1272 hasWriteExternalStoragePermission = true;
1273 } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
1274 hasReadExternalStoragePermission = true;
1275 } else if (name == "android.permission.READ_PHONE_STATE") {
1276 hasReadPhoneStatePermission = true;
1277 } else if (name == "android.permission.READ_CONTACTS") {
1278 hasReadContactsPermission = true;
1279 } else if (name == "android.permission.WRITE_CONTACTS") {
1280 hasWriteContactsPermission = true;
1281 } else if (name == "android.permission.READ_CALL_LOG") {
1282 hasReadCallLogPermission = true;
1283 } else if (name == "android.permission.WRITE_CALL_LOG") {
1284 hasWriteCallLogPermission = true;
1285 }
Adam Lesinski58f1f362013-11-12 12:59:08 -08001286
1287 printUsesPermission(name,
1288 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
1289 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
1290 } else {
Adam Lesinski282e1812014-01-23 18:17:42 -08001291 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1292 error.string());
1293 goto bail;
1294 }
1295 } else if (tag == "uses-package") {
1296 String8 name = getAttribute(tree, NAME_ATTR, &error);
1297 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001298 printf("uses-package:'%s'\n",
1299 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001300 } else {
1301 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1302 error.string());
1303 goto bail;
1304 }
1305 } else if (tag == "original-package") {
1306 String8 name = getAttribute(tree, NAME_ATTR, &error);
1307 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001308 printf("original-package:'%s'\n",
1309 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001310 } else {
1311 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1312 error.string());
1313 goto bail;
1314 }
1315 } else if (tag == "supports-gl-texture") {
1316 String8 name = getAttribute(tree, NAME_ATTR, &error);
1317 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001318 printf("supports-gl-texture:'%s'\n",
1319 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001320 } else {
1321 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1322 error.string());
1323 goto bail;
1324 }
1325 } else if (tag == "compatible-screens") {
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001326 printCompatibleScreens(tree, &error);
1327 if (error != "") {
1328 fprintf(stderr, "ERROR getting compatible screens: %s\n",
1329 error.string());
1330 goto bail;
1331 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001332 depth--;
1333 } else if (tag == "package-verifier") {
1334 String8 name = getAttribute(tree, NAME_ATTR, &error);
1335 if (name != "" && error == "") {
1336 String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
1337 if (publicKey != "" && error == "") {
1338 printf("package-verifier: name='%s' publicKey='%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001339 ResTable::normalizeForOutput(name.string()).string(),
1340 ResTable::normalizeForOutput(publicKey.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001341 }
1342 }
1343 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001344 } else if (depth == 3) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001345 withinActivity = false;
1346 withinReceiver = false;
1347 withinService = false;
1348 hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001349 hasMetaHostPaymentCategory = false;
1350 hasMetaOffHostPaymentCategory = false;
1351 hasBindDeviceAdminPermission = false;
1352 hasBindInputMethodPermission = false;
1353 hasBindAccessibilityServicePermission = false;
1354 hasBindPrintServicePermission = false;
1355 hasBindNfcServicePermission = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001356 if (withinApplication) {
1357 if(tag == "activity") {
1358 withinActivity = true;
1359 activityName = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001360 if (error != "") {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001361 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1362 error.string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001363 goto bail;
1364 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001365
Michael Wrightec4fdec2013-09-06 16:50:52 -07001366 activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1367 if (error != "") {
1368 fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
1369 error.string());
1370 goto bail;
1371 }
1372
1373 activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1374 if (error != "") {
1375 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
1376 error.string());
1377 goto bail;
1378 }
1379
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001380 activityBanner = getResolvedAttribute(&res, tree, BANNER_ATTR, &error);
1381 if (error != "") {
1382 fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
1383 error.string());
1384 goto bail;
1385 }
1386
Michael Wrightec4fdec2013-09-06 16:50:52 -07001387 int32_t orien = getResolvedIntegerAttribute(&res, tree,
1388 SCREEN_ORIENTATION_ATTR, &error);
1389 if (error == "") {
1390 if (orien == 0 || orien == 6 || orien == 8) {
1391 // Requests landscape, sensorLandscape, or reverseLandscape.
1392 reqScreenLandscapeFeature = true;
1393 } else if (orien == 1 || orien == 7 || orien == 9) {
1394 // Requests portrait, sensorPortrait, or reversePortrait.
1395 reqScreenPortraitFeature = true;
1396 }
1397 }
1398 } else if (tag == "uses-library") {
1399 String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
1400 if (error != "") {
1401 fprintf(stderr,
1402 "ERROR getting 'android:name' attribute for uses-library"
1403 " %s\n", error.string());
1404 goto bail;
1405 }
1406 int req = getIntegerAttribute(tree,
1407 REQUIRED_ATTR, NULL, 1);
1408 printf("uses-library%s:'%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001409 req ? "" : "-not-required", ResTable::normalizeForOutput(
1410 libraryName.string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001411 } else if (tag == "receiver") {
1412 withinReceiver = true;
1413 receiverName = getAttribute(tree, NAME_ATTR, &error);
1414
1415 if (error != "") {
1416 fprintf(stderr,
1417 "ERROR getting 'android:name' attribute for receiver:"
1418 " %s\n", error.string());
1419 goto bail;
1420 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001421
1422 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1423 if (error == "") {
1424 if (permission == "android.permission.BIND_DEVICE_ADMIN") {
1425 hasBindDeviceAdminPermission = true;
1426 }
1427 } else {
1428 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1429 " receiver '%s': %s\n", receiverName.string(), error.string());
1430 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001431 } else if (tag == "service") {
1432 withinService = true;
1433 serviceName = getAttribute(tree, NAME_ATTR, &error);
1434
1435 if (error != "") {
1436 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1437 "service:%s\n", error.string());
1438 goto bail;
1439 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001440
1441 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1442 if (error == "") {
1443 if (permission == "android.permission.BIND_INPUT_METHOD") {
1444 hasBindInputMethodPermission = true;
1445 } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
1446 hasBindAccessibilityServicePermission = true;
1447 } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
1448 hasBindPrintServicePermission = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001449 } else if (permission == "android.permission.BIND_NFC_SERVICE") {
1450 hasBindNfcServicePermission = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001451 }
1452 } else {
1453 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1454 " service '%s': %s\n", serviceName.string(), error.string());
1455 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001456 } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
1457 String8 metaDataName = getAttribute(tree, NAME_ATTR, &error);
1458 if (error != "") {
1459 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1460 "meta-data:%s\n", error.string());
1461 goto bail;
1462 }
Maurice Chu2675f762013-10-22 17:33:11 -07001463 printf("meta-data: name='%s' ",
1464 ResTable::normalizeForOutput(metaDataName.string()).string());
Maurice Chu76327312013-10-16 18:28:46 -07001465 printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"),
1466 &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001467 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001468 // Try looking for a RESOURCE_ATTR
1469 error = "";
1470 printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR,
1471 String8("resource"), &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001472 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001473 fprintf(stderr, "ERROR getting 'android:value' or "
1474 "'android:resource' attribute for "
1475 "meta-data:%s\n", error.string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001476 goto bail;
1477 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001478 }
Maurice Chu76327312013-10-16 18:28:46 -07001479 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001480 } else if (withinSupportsInput && tag == "input-type") {
1481 String8 name = getAttribute(tree, NAME_ATTR, &error);
1482 if (name != "" && error == "") {
1483 supportedInput.add(name);
1484 } else {
1485 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1486 error.string());
1487 goto bail;
1488 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001489 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001490 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001491 } else if (depth == 4) {
1492 if (tag == "intent-filter") {
1493 hasIntentFilter = true;
1494 withinIntentFilter = true;
1495 actMainActivity = false;
1496 actWidgetReceivers = false;
1497 actImeService = false;
1498 actWallpaperService = false;
1499 actAccessibilityService = false;
1500 actPrintService = false;
1501 actDeviceAdminEnabled = false;
1502 actHostApduService = false;
1503 actOffHostApduService = false;
1504 } else if (withinService && tag == "meta-data") {
1505 String8 name = getAttribute(tree, NAME_ATTR, &error);
1506 if (error != "") {
1507 fprintf(stderr, "ERROR getting 'android:name' attribute for"
1508 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1509 goto bail;
1510 }
1511
1512 if (name == "android.nfc.cardemulation.host_apdu_service" ||
1513 name == "android.nfc.cardemulation.off_host_apdu_service") {
1514 bool offHost = true;
1515 if (name == "android.nfc.cardemulation.host_apdu_service") {
1516 offHost = false;
1517 }
1518
1519 String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
1520 if (error != "") {
1521 fprintf(stderr, "ERROR getting 'android:resource' attribute for"
1522 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1523 goto bail;
1524 }
1525
1526 Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
1527 offHost, &error);
1528 if (error != "") {
1529 fprintf(stderr, "ERROR getting AID category for service '%s'\n",
1530 serviceName.string());
1531 goto bail;
1532 }
1533
1534 const size_t catLen = categories.size();
1535 for (size_t i = 0; i < catLen; i++) {
1536 bool paymentCategory = (categories[i] == "payment");
1537 if (offHost) {
1538 hasMetaOffHostPaymentCategory |= paymentCategory;
1539 } else {
1540 hasMetaHostPaymentCategory |= paymentCategory;
1541 }
1542 }
1543 }
1544 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001545 } else if ((depth == 5) && withinIntentFilter) {
1546 String8 action;
1547 if (tag == "action") {
1548 action = getAttribute(tree, NAME_ATTR, &error);
1549 if (error != "") {
1550 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1551 error.string());
1552 goto bail;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001553 }
1554
Adam Lesinskia5018c92013-09-30 16:23:15 -07001555 if (withinActivity) {
1556 if (action == "android.intent.action.MAIN") {
1557 isMainActivity = true;
1558 actMainActivity = true;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001559 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001560 } else if (withinReceiver) {
1561 if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
1562 actWidgetReceivers = true;
1563 } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
1564 actDeviceAdminEnabled = true;
1565 }
1566 } else if (withinService) {
1567 if (action == "android.view.InputMethod") {
1568 actImeService = true;
1569 } else if (action == "android.service.wallpaper.WallpaperService") {
1570 actWallpaperService = true;
1571 } else if (action == "android.accessibilityservice.AccessibilityService") {
1572 actAccessibilityService = true;
1573 } else if (action == "android.printservice.PrintService") {
1574 actPrintService = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001575 } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
1576 actHostApduService = true;
1577 } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
1578 actOffHostApduService = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001579 }
1580 }
1581 if (action == "android.intent.action.SEARCH") {
1582 isSearchable = true;
1583 }
1584 }
1585
1586 if (tag == "category") {
1587 String8 category = getAttribute(tree, NAME_ATTR, &error);
1588 if (error != "") {
1589 fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
1590 error.string());
1591 goto bail;
1592 }
1593 if (withinActivity) {
1594 if (category == "android.intent.category.LAUNCHER") {
1595 isLauncherActivity = true;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001596 } else if (category == "android.intent.category.LEANBACK_LAUNCHER") {
1597 isLeanbackLauncherActivity = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001598 }
1599 }
1600 }
1601 }
1602 }
1603
1604 // Pre-1.6 implicitly granted permission compatibility logic
1605 if (targetSdk < 4) {
1606 if (!hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001607 printUsesPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"));
1608 printUsesImpliedPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"),
1609 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001610 hasWriteExternalStoragePermission = true;
1611 }
1612 if (!hasReadPhoneStatePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001613 printUsesPermission(String8("android.permission.READ_PHONE_STATE"));
1614 printUsesImpliedPermission(String8("android.permission.READ_PHONE_STATE"),
1615 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001616 }
1617 }
1618
1619 // If the application has requested WRITE_EXTERNAL_STORAGE, we will
1620 // force them to always take READ_EXTERNAL_STORAGE as well. We always
1621 // do this (regardless of target API version) because we can't have
1622 // an app with write permission but not read permission.
1623 if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001624 printUsesPermission(String8("android.permission.READ_EXTERNAL_STORAGE"));
1625 printUsesImpliedPermission(String8("android.permission.READ_EXTERNAL_STORAGE"),
1626 String8("requested WRITE_EXTERNAL_STORAGE"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001627 }
1628
1629 // Pre-JellyBean call log permission compatibility.
1630 if (targetSdk < 16) {
1631 if (!hasReadCallLogPermission && hasReadContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001632 printUsesPermission(String8("android.permission.READ_CALL_LOG"));
1633 printUsesImpliedPermission(String8("android.permission.READ_CALL_LOG"),
1634 String8("targetSdkVersion < 16 and requested READ_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001635 }
1636 if (!hasWriteCallLogPermission && hasWriteContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001637 printUsesPermission(String8("android.permission.WRITE_CALL_LOG"));
1638 printUsesImpliedPermission(String8("android.permission.WRITE_CALL_LOG"),
1639 String8("targetSdkVersion < 16 and requested WRITE_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001640 }
1641 }
1642
1643 /* The following blocks handle printing "inferred" uses-features, based
1644 * on whether related features or permissions are used by the app.
1645 * Note that the various spec*Feature variables denote whether the
1646 * relevant tag was *present* in the AndroidManfest, not that it was
1647 * present and set to true.
1648 */
1649 // Camera-related back-compatibility logic
1650 if (!specCameraFeature) {
1651 if (reqCameraFlashFeature) {
1652 // if app requested a sub-feature (autofocus or flash) and didn't
1653 // request the base camera feature, we infer that it meant to
1654 printf("uses-feature:'android.hardware.camera'\n");
1655 printf("uses-implied-feature:'android.hardware.camera'," \
1656 "'requested android.hardware.camera.flash feature'\n");
1657 } else if (reqCameraAutofocusFeature) {
1658 // if app requested a sub-feature (autofocus or flash) and didn't
1659 // request the base camera feature, we infer that it meant to
1660 printf("uses-feature:'android.hardware.camera'\n");
1661 printf("uses-implied-feature:'android.hardware.camera'," \
1662 "'requested android.hardware.camera.autofocus feature'\n");
1663 } else if (hasCameraPermission) {
Maurice Chu2675f762013-10-22 17:33:11 -07001664 // if app wants to use camera but didn't request the feature, we infer
Adam Lesinski282e1812014-01-23 18:17:42 -08001665 // that it meant to, and further that it wants autofocus
1666 // (which was the 1.0 - 1.5 behavior)
1667 printf("uses-feature:'android.hardware.camera'\n");
1668 if (!specCameraAutofocusFeature) {
1669 printf("uses-feature:'android.hardware.camera.autofocus'\n");
1670 printf("uses-implied-feature:'android.hardware.camera.autofocus'," \
1671 "'requested android.permission.CAMERA permission'\n");
1672 }
1673 }
1674 }
1675
1676 // Location-related back-compatibility logic
1677 if (!specLocationFeature &&
1678 (hasMockLocPermission || hasCoarseLocPermission || hasGpsPermission ||
1679 hasGeneralLocPermission || reqNetworkLocFeature || reqGpsFeature)) {
1680 // if app either takes a location-related permission or requests one of the
1681 // sub-features, we infer that it also meant to request the base location feature
1682 printf("uses-feature:'android.hardware.location'\n");
1683 printf("uses-implied-feature:'android.hardware.location'," \
1684 "'requested a location access permission'\n");
1685 }
1686 if (!specGpsFeature && hasGpsPermission) {
1687 // if app takes GPS (FINE location) perm but does not request the GPS
1688 // feature, we infer that it meant to
1689 printf("uses-feature:'android.hardware.location.gps'\n");
1690 printf("uses-implied-feature:'android.hardware.location.gps'," \
1691 "'requested android.permission.ACCESS_FINE_LOCATION permission'\n");
1692 }
1693 if (!specNetworkLocFeature && hasCoarseLocPermission) {
1694 // if app takes Network location (COARSE location) perm but does not request the
1695 // network location feature, we infer that it meant to
1696 printf("uses-feature:'android.hardware.location.network'\n");
1697 printf("uses-implied-feature:'android.hardware.location.network'," \
1698 "'requested android.permission.ACCESS_COARSE_LOCATION permission'\n");
1699 }
1700
1701 // Bluetooth-related compatibility logic
1702 if (!specBluetoothFeature && hasBluetoothPermission && (targetSdk > 4)) {
1703 // if app takes a Bluetooth permission but does not request the Bluetooth
1704 // feature, we infer that it meant to
1705 printf("uses-feature:'android.hardware.bluetooth'\n");
1706 printf("uses-implied-feature:'android.hardware.bluetooth'," \
1707 "'requested android.permission.BLUETOOTH or android.permission.BLUETOOTH_ADMIN " \
1708 "permission and targetSdkVersion > 4'\n");
1709 }
1710
1711 // Microphone-related compatibility logic
1712 if (!specMicrophoneFeature && hasRecordAudioPermission) {
1713 // if app takes the record-audio permission but does not request the microphone
1714 // feature, we infer that it meant to
1715 printf("uses-feature:'android.hardware.microphone'\n");
1716 printf("uses-implied-feature:'android.hardware.microphone'," \
1717 "'requested android.permission.RECORD_AUDIO permission'\n");
1718 }
1719
1720 // WiFi-related compatibility logic
1721 if (!specWiFiFeature && hasWiFiPermission) {
1722 // if app takes one of the WiFi permissions but does not request the WiFi
1723 // feature, we infer that it meant to
1724 printf("uses-feature:'android.hardware.wifi'\n");
1725 printf("uses-implied-feature:'android.hardware.wifi'," \
1726 "'requested android.permission.ACCESS_WIFI_STATE, " \
1727 "android.permission.CHANGE_WIFI_STATE, or " \
1728 "android.permission.CHANGE_WIFI_MULTICAST_STATE permission'\n");
1729 }
1730
1731 // Telephony-related compatibility logic
1732 if (!specTelephonyFeature && (hasTelephonyPermission || reqTelephonySubFeature)) {
1733 // if app takes one of the telephony permissions or requests a sub-feature but
1734 // does not request the base telephony feature, we infer that it meant to
1735 printf("uses-feature:'android.hardware.telephony'\n");
1736 printf("uses-implied-feature:'android.hardware.telephony'," \
1737 "'requested a telephony-related permission or feature'\n");
1738 }
1739
1740 // Touchscreen-related back-compatibility logic
1741 if (!specTouchscreenFeature) { // not a typo!
1742 // all apps are presumed to require a touchscreen, unless they explicitly say
1743 // <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
1744 // Note that specTouchscreenFeature is true if the tag is present, regardless
1745 // of whether its value is true or false, so this is safe
1746 printf("uses-feature:'android.hardware.touchscreen'\n");
1747 printf("uses-implied-feature:'android.hardware.touchscreen'," \
1748 "'assumed you require a touch screen unless explicitly made optional'\n");
1749 }
1750 if (!specMultitouchFeature && reqDistinctMultitouchFeature) {
1751 // if app takes one of the telephony permissions or requests a sub-feature but
1752 // does not request the base telephony feature, we infer that it meant to
1753 printf("uses-feature:'android.hardware.touchscreen.multitouch'\n");
1754 printf("uses-implied-feature:'android.hardware.touchscreen.multitouch'," \
1755 "'requested android.hardware.touchscreen.multitouch.distinct feature'\n");
1756 }
1757
1758 // Landscape/portrait-related compatibility logic
1759 if (!specScreenLandscapeFeature && !specScreenPortraitFeature) {
1760 // If the app has specified any activities in its manifest
1761 // that request a specific orientation, then assume that
1762 // orientation is required.
1763 if (reqScreenLandscapeFeature) {
1764 printf("uses-feature:'android.hardware.screen.landscape'\n");
1765 printf("uses-implied-feature:'android.hardware.screen.landscape'," \
1766 "'one or more activities have specified a landscape orientation'\n");
1767 }
1768 if (reqScreenPortraitFeature) {
1769 printf("uses-feature:'android.hardware.screen.portrait'\n");
1770 printf("uses-implied-feature:'android.hardware.screen.portrait'," \
1771 "'one or more activities have specified a portrait orientation'\n");
1772 }
1773 }
1774
1775 if (hasMainActivity) {
1776 printf("main\n");
1777 }
1778 if (hasWidgetReceivers) {
1779 printf("app-widget\n");
1780 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001781 if (hasDeviceAdminReceiver) {
1782 printf("device-admin\n");
1783 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001784 if (hasImeService) {
1785 printf("ime\n");
1786 }
1787 if (hasWallpaperService) {
1788 printf("wallpaper\n");
1789 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001790 if (hasAccessibilityService) {
1791 printf("accessibility\n");
1792 }
1793 if (hasPrintService) {
1794 printf("print\n");
1795 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001796 if (hasPaymentService) {
1797 printf("payment\n");
1798 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001799 if (hasOtherActivities) {
1800 printf("other-activities\n");
1801 }
1802 if (isSearchable) {
1803 printf("search\n");
1804 }
1805 if (hasOtherReceivers) {
1806 printf("other-receivers\n");
1807 }
1808 if (hasOtherServices) {
1809 printf("other-services\n");
1810 }
1811
1812 // For modern apps, if screen size buckets haven't been specified
1813 // but the new width ranges have, then infer the buckets from them.
1814 if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0
1815 && requiresSmallestWidthDp > 0) {
1816 int compatWidth = compatibleWidthLimitDp;
1817 if (compatWidth <= 0) {
1818 compatWidth = requiresSmallestWidthDp;
1819 }
1820 if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) {
1821 smallScreen = -1;
1822 } else {
1823 smallScreen = 0;
1824 }
1825 if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) {
1826 normalScreen = -1;
1827 } else {
1828 normalScreen = 0;
1829 }
1830 if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) {
1831 largeScreen = -1;
1832 } else {
1833 largeScreen = 0;
1834 }
1835 if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) {
1836 xlargeScreen = -1;
1837 } else {
1838 xlargeScreen = 0;
1839 }
1840 }
1841
1842 // Determine default values for any unspecified screen sizes,
1843 // based on the target SDK of the package. As of 4 (donut)
1844 // the screen size support was introduced, so all default to
1845 // enabled.
1846 if (smallScreen > 0) {
1847 smallScreen = targetSdk >= 4 ? -1 : 0;
1848 }
1849 if (normalScreen > 0) {
1850 normalScreen = -1;
1851 }
1852 if (largeScreen > 0) {
1853 largeScreen = targetSdk >= 4 ? -1 : 0;
1854 }
1855 if (xlargeScreen > 0) {
1856 // Introduced in Gingerbread.
1857 xlargeScreen = targetSdk >= 9 ? -1 : 0;
1858 }
1859 if (anyDensity > 0) {
1860 anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0
1861 || compatibleWidthLimitDp > 0) ? -1 : 0;
1862 }
1863 printf("supports-screens:");
1864 if (smallScreen != 0) {
1865 printf(" 'small'");
1866 }
1867 if (normalScreen != 0) {
1868 printf(" 'normal'");
1869 }
1870 if (largeScreen != 0) {
1871 printf(" 'large'");
1872 }
1873 if (xlargeScreen != 0) {
1874 printf(" 'xlarge'");
1875 }
1876 printf("\n");
1877 printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
1878 if (requiresSmallestWidthDp > 0) {
1879 printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp);
1880 }
1881 if (compatibleWidthLimitDp > 0) {
1882 printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp);
1883 }
1884 if (largestWidthLimitDp > 0) {
1885 printf("largest-width-limit:'%d'\n", largestWidthLimitDp);
1886 }
1887
1888 printf("locales:");
1889 const size_t NL = locales.size();
1890 for (size_t i=0; i<NL; i++) {
1891 const char* localeStr = locales[i].string();
1892 if (localeStr == NULL || strlen(localeStr) == 0) {
1893 localeStr = "--_--";
1894 }
1895 printf(" '%s'", localeStr);
1896 }
1897 printf("\n");
1898
1899 printf("densities:");
1900 const size_t ND = densities.size();
1901 for (size_t i=0; i<ND; i++) {
1902 printf(" '%d'", densities[i]);
1903 }
1904 printf("\n");
1905
1906 AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
1907 if (dir != NULL) {
1908 if (dir->getFileCount() > 0) {
1909 printf("native-code:");
1910 for (size_t i=0; i<dir->getFileCount(); i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07001911 printf(" '%s'", ResTable::normalizeForOutput(
1912 dir->getFileName(i).string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001913 }
1914 printf("\n");
1915 }
1916 delete dir;
1917 }
1918 } else if (strcmp("badger", option) == 0) {
1919 printf("%s", CONSOLE_DATA);
1920 } else if (strcmp("configurations", option) == 0) {
1921 Vector<ResTable_config> configs;
1922 res.getConfigurations(&configs);
1923 const size_t N = configs.size();
1924 for (size_t i=0; i<N; i++) {
1925 printf("%s\n", configs[i].toString().string());
1926 }
1927 } else {
1928 fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
1929 goto bail;
1930 }
1931 }
1932
1933 result = NO_ERROR;
1934
1935bail:
1936 if (asset) {
1937 delete asset;
1938 }
1939 return (result != NO_ERROR);
1940}
1941
1942
1943/*
1944 * Handle the "add" command, which wants to add files to a new or
1945 * pre-existing archive.
1946 */
1947int doAdd(Bundle* bundle)
1948{
1949 ZipFile* zip = NULL;
1950 status_t result = UNKNOWN_ERROR;
1951 const char* zipFileName;
1952
1953 if (bundle->getUpdate()) {
1954 /* avoid confusion */
1955 fprintf(stderr, "ERROR: can't use '-u' with add\n");
1956 goto bail;
1957 }
1958
1959 if (bundle->getFileSpecCount() < 1) {
1960 fprintf(stderr, "ERROR: must specify zip file name\n");
1961 goto bail;
1962 }
1963 zipFileName = bundle->getFileSpecEntry(0);
1964
1965 if (bundle->getFileSpecCount() < 2) {
1966 fprintf(stderr, "NOTE: nothing to do\n");
1967 goto bail;
1968 }
1969
1970 zip = openReadWrite(zipFileName, true);
1971 if (zip == NULL) {
1972 fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
1973 goto bail;
1974 }
1975
1976 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
1977 const char* fileName = bundle->getFileSpecEntry(i);
1978
1979 if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
1980 printf(" '%s'... (from gzip)\n", fileName);
1981 result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
1982 } else {
1983 if (bundle->getJunkPath()) {
1984 String8 storageName = String8(fileName).getPathLeaf();
Maurice Chu2675f762013-10-22 17:33:11 -07001985 printf(" '%s' as '%s'...\n", fileName,
1986 ResTable::normalizeForOutput(storageName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001987 result = zip->add(fileName, storageName.string(),
1988 bundle->getCompressionMethod(), NULL);
1989 } else {
1990 printf(" '%s'...\n", fileName);
1991 result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
1992 }
1993 }
1994 if (result != NO_ERROR) {
1995 fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
1996 if (result == NAME_NOT_FOUND) {
1997 fprintf(stderr, ": file not found\n");
1998 } else if (result == ALREADY_EXISTS) {
1999 fprintf(stderr, ": already exists in archive\n");
2000 } else {
2001 fprintf(stderr, "\n");
2002 }
2003 goto bail;
2004 }
2005 }
2006
2007 result = NO_ERROR;
2008
2009bail:
2010 delete zip;
2011 return (result != NO_ERROR);
2012}
2013
2014
2015/*
2016 * Delete files from an existing archive.
2017 */
2018int doRemove(Bundle* bundle)
2019{
2020 ZipFile* zip = NULL;
2021 status_t result = UNKNOWN_ERROR;
2022 const char* zipFileName;
2023
2024 if (bundle->getFileSpecCount() < 1) {
2025 fprintf(stderr, "ERROR: must specify zip file name\n");
2026 goto bail;
2027 }
2028 zipFileName = bundle->getFileSpecEntry(0);
2029
2030 if (bundle->getFileSpecCount() < 2) {
2031 fprintf(stderr, "NOTE: nothing to do\n");
2032 goto bail;
2033 }
2034
2035 zip = openReadWrite(zipFileName, false);
2036 if (zip == NULL) {
2037 fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
2038 zipFileName);
2039 goto bail;
2040 }
2041
2042 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2043 const char* fileName = bundle->getFileSpecEntry(i);
2044 ZipEntry* entry;
2045
2046 entry = zip->getEntryByName(fileName);
2047 if (entry == NULL) {
2048 printf(" '%s' NOT FOUND\n", fileName);
2049 continue;
2050 }
2051
2052 result = zip->remove(entry);
2053
2054 if (result != NO_ERROR) {
2055 fprintf(stderr, "Unable to delete '%s' from '%s'\n",
2056 bundle->getFileSpecEntry(i), zipFileName);
2057 goto bail;
2058 }
2059 }
2060
2061 /* update the archive */
2062 zip->flush();
2063
2064bail:
2065 delete zip;
2066 return (result != NO_ERROR);
2067}
2068
Adam Lesinskifab50872014-04-16 14:40:42 -07002069static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder) {
2070 const size_t numDirs = dir->getDirs().size();
2071 for (size_t i = 0; i < numDirs; i++) {
2072 status_t err = addResourcesToBuilder(dir->getDirs().valueAt(i), builder);
2073 if (err != NO_ERROR) {
2074 return err;
2075 }
2076 }
2077
2078 const size_t numFiles = dir->getFiles().size();
2079 for (size_t i = 0; i < numFiles; i++) {
2080 sp<AaptGroup> gp = dir->getFiles().valueAt(i);
2081 const size_t numConfigs = gp->getFiles().size();
2082 for (size_t j = 0; j < numConfigs; j++) {
2083 status_t err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2084 if (err != NO_ERROR) {
2085 fprintf(stderr, "Failed to add %s (%s) to builder.\n",
2086 gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string());
2087 return err;
2088 }
2089 }
2090 }
2091 return NO_ERROR;
2092}
2093
2094static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) {
2095 if (split->isBase()) {
2096 return original;
2097 }
2098
2099 String8 ext(original.getPathExtension());
2100 if (ext == String8(".apk")) {
2101 return String8::format("%s_%s%s",
2102 original.getBasePath().string(),
2103 split->getDirectorySafeName().string(),
2104 ext.string());
2105 }
2106
2107 return String8::format("%s_%s", original.string(),
2108 split->getDirectorySafeName().string());
2109}
Adam Lesinski282e1812014-01-23 18:17:42 -08002110
2111/*
2112 * Package up an asset directory and associated application files.
2113 */
2114int doPackage(Bundle* bundle)
2115{
2116 const char* outputAPKFile;
2117 int retVal = 1;
2118 status_t err;
2119 sp<AaptAssets> assets;
2120 int N;
2121 FILE* fp;
2122 String8 dependencyFile;
Adam Lesinskifab50872014-04-16 14:40:42 -07002123 sp<ApkBuilder> builder;
Adam Lesinski282e1812014-01-23 18:17:42 -08002124
Anton Krumina2ef5c02014-03-12 14:46:44 -07002125 // -c en_XA or/and ar_XB means do pseudolocalization
Adam Lesinskifab50872014-04-16 14:40:42 -07002126 sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
2127 err = configFilter->parse(bundle->getConfigurations());
Adam Lesinski282e1812014-01-23 18:17:42 -08002128 if (err != NO_ERROR) {
2129 goto bail;
2130 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002131 if (configFilter->containsPseudo()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002132 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
2133 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002134 if (configFilter->containsPseudoBidi()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002135 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
Adam Lesinski282e1812014-01-23 18:17:42 -08002136 }
2137
2138 N = bundle->getFileSpecCount();
2139 if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
Adam Lesinski09384302014-01-22 16:07:42 -08002140 && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002141 fprintf(stderr, "ERROR: no input files\n");
2142 goto bail;
2143 }
2144
2145 outputAPKFile = bundle->getOutputAPKFile();
2146
2147 // Make sure the filenames provided exist and are of the appropriate type.
2148 if (outputAPKFile) {
2149 FileType type;
2150 type = getFileType(outputAPKFile);
2151 if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
2152 fprintf(stderr,
2153 "ERROR: output file '%s' exists but is not regular file\n",
2154 outputAPKFile);
2155 goto bail;
2156 }
2157 }
2158
2159 // Load the assets.
2160 assets = new AaptAssets();
2161
2162 // Set up the resource gathering in assets if we're going to generate
2163 // dependency files. Every time we encounter a resource while slurping
2164 // the tree, we'll add it to these stores so we have full resource paths
2165 // to write to a dependency file.
2166 if (bundle->getGenDependencies()) {
2167 sp<FilePathStore> resPathStore = new FilePathStore;
2168 assets->setFullResPaths(resPathStore);
2169 sp<FilePathStore> assetPathStore = new FilePathStore;
2170 assets->setFullAssetPaths(assetPathStore);
2171 }
2172
2173 err = assets->slurpFromArgs(bundle);
2174 if (err < 0) {
2175 goto bail;
2176 }
2177
2178 if (bundle->getVerbose()) {
2179 assets->print(String8());
2180 }
2181
Adam Lesinskifab50872014-04-16 14:40:42 -07002182 // Create the ApkBuilder, which will collect the compiled files
2183 // to write to the final APK (or sets of APKs if we are building
2184 // a Split APK.
2185 builder = new ApkBuilder(configFilter);
2186
2187 // If we are generating a Split APK, find out which configurations to split on.
2188 if (bundle->getSplitConfigurations().size() > 0) {
2189 const Vector<String8>& splitStrs = bundle->getSplitConfigurations();
2190 const size_t numSplits = splitStrs.size();
2191 for (size_t i = 0; i < numSplits; i++) {
2192 std::set<ConfigDescription> configs;
2193 if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {
2194 fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());
2195 goto bail;
2196 }
2197
2198 err = builder->createSplitForConfigs(configs);
2199 if (err != NO_ERROR) {
2200 goto bail;
2201 }
2202 }
2203 }
2204
Adam Lesinski282e1812014-01-23 18:17:42 -08002205 // If they asked for any fileAs that need to be compiled, do so.
2206 if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002207 err = buildResources(bundle, assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002208 if (err != 0) {
2209 goto bail;
2210 }
2211 }
2212
2213 // At this point we've read everything and processed everything. From here
2214 // on out it's just writing output files.
2215 if (SourcePos::hasErrors()) {
2216 goto bail;
2217 }
2218
2219 // Update symbols with information about which ones are needed as Java symbols.
2220 assets->applyJavaSymbols();
2221 if (SourcePos::hasErrors()) {
2222 goto bail;
2223 }
2224
2225 // If we've been asked to generate a dependency file, do that here
2226 if (bundle->getGenDependencies()) {
2227 // If this is the packaging step, generate the dependency file next to
2228 // the output apk (e.g. bin/resources.ap_.d)
2229 if (outputAPKFile) {
2230 dependencyFile = String8(outputAPKFile);
2231 // Add the .d extension to the dependency file.
2232 dependencyFile.append(".d");
2233 } else {
2234 // Else if this is the R.java dependency generation step,
2235 // generate the dependency file in the R.java package subdirectory
2236 // e.g. gen/com/foo/app/R.java.d
2237 dependencyFile = String8(bundle->getRClassDir());
2238 dependencyFile.appendPath("R.java.d");
2239 }
2240 // Make sure we have a clean dependency file to start with
2241 fp = fopen(dependencyFile, "w");
2242 fclose(fp);
2243 }
2244
2245 // Write out R.java constants
2246 if (!assets->havePrivateSymbols()) {
2247 if (bundle->getCustomPackage() == NULL) {
2248 // Write the R.java file into the appropriate class directory
2249 // e.g. gen/com/foo/app/R.java
2250 err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
2251 } else {
2252 const String8 customPkg(bundle->getCustomPackage());
2253 err = writeResourceSymbols(bundle, assets, customPkg, true);
2254 }
2255 if (err < 0) {
2256 goto bail;
2257 }
2258 // If we have library files, we're going to write our R.java file into
2259 // the appropriate class directory for those libraries as well.
2260 // e.g. gen/com/foo/app/lib/R.java
2261 if (bundle->getExtraPackages() != NULL) {
2262 // Split on colon
2263 String8 libs(bundle->getExtraPackages());
2264 char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
2265 while (packageString != NULL) {
2266 // Write the R.java file out with the correct package name
2267 err = writeResourceSymbols(bundle, assets, String8(packageString), true);
2268 if (err < 0) {
2269 goto bail;
2270 }
2271 packageString = strtok(NULL, ":");
2272 }
2273 libs.unlockBuffer();
2274 }
2275 } else {
2276 err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
2277 if (err < 0) {
2278 goto bail;
2279 }
2280 err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
2281 if (err < 0) {
2282 goto bail;
2283 }
2284 }
2285
2286 // Write out the ProGuard file
2287 err = writeProguardFile(bundle, assets);
2288 if (err < 0) {
2289 goto bail;
2290 }
2291
2292 // Write the apk
2293 if (outputAPKFile) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002294 // Gather all resources and add them to the APK Builder. The builder will then
2295 // figure out which Split they belong in.
2296 err = addResourcesToBuilder(assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002297 if (err != NO_ERROR) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002298 goto bail;
2299 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002300
2301 const Vector<sp<ApkSplit> >& splits = builder->getSplits();
2302 const size_t numSplits = splits.size();
2303 for (size_t i = 0; i < numSplits; i++) {
2304 const sp<ApkSplit>& split = splits[i];
2305 String8 outputPath = buildApkName(String8(outputAPKFile), split);
2306 err = writeAPK(bundle, outputPath, split);
2307 if (err != NO_ERROR) {
2308 fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
2309 goto bail;
2310 }
2311 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002312 }
2313
2314 // If we've been asked to generate a dependency file, we need to finish up here.
2315 // the writeResourceSymbols and writeAPK functions have already written the target
2316 // half of the dependency file, now we need to write the prerequisites. (files that
2317 // the R.java file or .ap_ file depend on)
2318 if (bundle->getGenDependencies()) {
2319 // Now that writeResourceSymbols or writeAPK has taken care of writing
2320 // the targets to our dependency file, we'll write the prereqs
2321 fp = fopen(dependencyFile, "a+");
2322 fprintf(fp, " : ");
2323 bool includeRaw = (outputAPKFile != NULL);
2324 err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
2325 // Also manually add the AndroidManifeset since it's not under res/ or assets/
2326 // and therefore was not added to our pathstores during slurping
2327 fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
2328 fclose(fp);
2329 }
2330
2331 retVal = 0;
2332bail:
2333 if (SourcePos::hasErrors()) {
2334 SourcePos::printErrors(stderr);
2335 }
2336 return retVal;
2337}
2338
2339/*
2340 * Do PNG Crunching
2341 * PRECONDITIONS
2342 * -S flag points to a source directory containing drawable* folders
2343 * -C flag points to destination directory. The folder structure in the
2344 * source directory will be mirrored to the destination (cache) directory
2345 *
2346 * POSTCONDITIONS
2347 * Destination directory will be updated to match the PNG files in
Maurice Chu2675f762013-10-22 17:33:11 -07002348 * the source directory.
Adam Lesinski282e1812014-01-23 18:17:42 -08002349 */
2350int doCrunch(Bundle* bundle)
2351{
2352 fprintf(stdout, "Crunching PNG Files in ");
2353 fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
2354 fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
2355
2356 updatePreProcessedCache(bundle);
2357
2358 return NO_ERROR;
2359}
2360
2361/*
2362 * Do PNG Crunching on a single flag
2363 * -i points to a single png file
2364 * -o points to a single png output file
2365 */
2366int doSingleCrunch(Bundle* bundle)
2367{
2368 fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile());
2369 fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile());
2370
2371 String8 input(bundle->getSingleCrunchInputFile());
2372 String8 output(bundle->getSingleCrunchOutputFile());
2373
2374 if (preProcessImageToCache(bundle, input, output) != NO_ERROR) {
2375 // we can't return the status_t as it gets truncate to the lower 8 bits.
2376 return 42;
2377 }
2378
2379 return NO_ERROR;
2380}
2381
2382char CONSOLE_DATA[2925] = {
2383 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2384 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2385 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2386 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2387 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63,
2388 86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83,
2389 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2390 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2391 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81,
2392 81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32,
2393 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2394 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2395 32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59,
2396 59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2397 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2398 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59,
2399 59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32,
2400 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2401 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2402 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87,
2403 58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32,
2404 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2405 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2406 47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58,
2407 121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2408 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2409 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81,
2410 81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32,
2411 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2412 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2413 32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59,
2414 59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2415 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2416 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59,
2417 59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32,
2418 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2419 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2420 32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81,
2421 70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32,
2422 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2423 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2424 32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81,
2425 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2426 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2427 32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109,
2428 81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32,
2429 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2430 32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59,
2431 59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59,
2432 60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2433 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59,
2434 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46,
2435 61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32,
2436 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2437 32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2438 59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81,
2439 109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32,
2440 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81,
2441 67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2442 59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58,
2443 61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32,
2444 32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59,
2445 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37,
2446 73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2447 59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2448 32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59,
2449 59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96,
2450 46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61,
2451 97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32,
2452 46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61,
2453 119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59,
2454 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32,
2455 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2456 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119,
2457 119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2458 59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10,
2459 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2460 32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81,
2461 81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41,
2462 87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2463 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81,
2464 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58,
2465 45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32,
2466 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2467 32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2468 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32,
2469 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2470 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58,
2471 59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58,
2472 59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2473 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2474 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81,
2475 81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32,
2476 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2477 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2478 32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2479 81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2480 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2481 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106,
2482 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59,
2483 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2484 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2485 32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2486 81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32,
2487 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2488 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2489 58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59,
2490 61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2491 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2492 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81,
2493 81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32,
2494 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2495 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2496 32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2497 81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2498 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2499 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59,
2500 59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59,
2501 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2502 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2503 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33,
2504 58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32,
2505 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2506 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2507 61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59,
2508 59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32,
2509 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32,
2510 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95,
2511 32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59,
2512 61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2513 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2514 32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45,
2515 59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32,
2516 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32,
2517 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59,
2518 59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59,
2519 59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2520 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2521 32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2522 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2523 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2524 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2525 46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61,
2526 46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2527 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2528 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59,
2529 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59,
2530 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2531 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2532 32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32,
2533 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32,
2534 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2535 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61,
2536 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59,
2537 59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2538 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2539 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32,
2540 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32,
2541 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2542 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2543 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2544 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2545 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10
2546 };