blob: 22b7d355ab7b6fe646e1adc94139141a024447bd [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//
6#include "Main.h"
7#include "Bundle.h"
8#include "ResourceFilter.h"
9#include "ResourceTable.h"
10#include "Images.h"
11#include "XMLNode.h"
12
13#include <utils/Log.h>
14#include <utils/threads.h>
15#include <utils/List.h>
16#include <utils/Errors.h>
17
18#include <fcntl.h>
19#include <errno.h>
20
21using namespace android;
22
23/*
24 * Show version info. All the cool kids do it.
25 */
26int doVersion(Bundle* bundle)
27{
28 if (bundle->getFileSpecCount() != 0) {
29 printf("(ignoring extra arguments)\n");
30 }
31 printf("Android Asset Packaging Tool, v0.2\n");
32
33 return 0;
34}
35
36
37/*
38 * Open the file read only. The call fails if the file doesn't exist.
39 *
40 * Returns NULL on failure.
41 */
42ZipFile* openReadOnly(const char* fileName)
43{
44 ZipFile* zip;
45 status_t result;
46
47 zip = new ZipFile;
48 result = zip->open(fileName, ZipFile::kOpenReadOnly);
49 if (result != NO_ERROR) {
50 if (result == NAME_NOT_FOUND) {
51 fprintf(stderr, "ERROR: '%s' not found\n", fileName);
52 } else if (result == PERMISSION_DENIED) {
53 fprintf(stderr, "ERROR: '%s' access denied\n", fileName);
54 } else {
55 fprintf(stderr, "ERROR: failed opening '%s' as Zip file\n",
56 fileName);
57 }
58 delete zip;
59 return NULL;
60 }
61
62 return zip;
63}
64
65/*
66 * Open the file read-write. The file will be created if it doesn't
67 * already exist and "okayToCreate" is set.
68 *
69 * Returns NULL on failure.
70 */
71ZipFile* openReadWrite(const char* fileName, bool okayToCreate)
72{
73 ZipFile* zip = NULL;
74 status_t result;
75 int flags;
76
77 flags = ZipFile::kOpenReadWrite;
78 if (okayToCreate) {
79 flags |= ZipFile::kOpenCreate;
80 }
81
82 zip = new ZipFile;
83 result = zip->open(fileName, flags);
84 if (result != NO_ERROR) {
85 delete zip;
86 zip = NULL;
87 goto bail;
88 }
89
90bail:
91 return zip;
92}
93
94
95/*
96 * Return a short string describing the compression method.
97 */
98const char* compressionName(int method)
99{
100 if (method == ZipEntry::kCompressStored) {
101 return "Stored";
102 } else if (method == ZipEntry::kCompressDeflated) {
103 return "Deflated";
104 } else {
105 return "Unknown";
106 }
107}
108
109/*
110 * Return the percent reduction in size (0% == no compression).
111 */
112int calcPercent(long uncompressedLen, long compressedLen)
113{
114 if (!uncompressedLen) {
115 return 0;
116 } else {
117 return (int) (100.0 - (compressedLen * 100.0) / uncompressedLen + 0.5);
118 }
119}
120
121/*
122 * Handle the "list" command, which can be a simple file dump or
123 * a verbose listing.
124 *
125 * The verbose listing closely matches the output of the Info-ZIP "unzip"
126 * command.
127 */
128int doList(Bundle* bundle)
129{
130 int result = 1;
131 ZipFile* zip = NULL;
132 const ZipEntry* entry;
133 long totalUncLen, totalCompLen;
134 const char* zipFileName;
135
136 if (bundle->getFileSpecCount() != 1) {
137 fprintf(stderr, "ERROR: specify zip file name (only)\n");
138 goto bail;
139 }
140 zipFileName = bundle->getFileSpecEntry(0);
141
142 zip = openReadOnly(zipFileName);
143 if (zip == NULL) {
144 goto bail;
145 }
146
147 int count, i;
148
149 if (bundle->getVerbose()) {
150 printf("Archive: %s\n", zipFileName);
151 printf(
152 " Length Method Size Ratio Offset Date Time CRC-32 Name\n");
153 printf(
154 "-------- ------ ------- ----- ------- ---- ---- ------ ----\n");
155 }
156
157 totalUncLen = totalCompLen = 0;
158
159 count = zip->getNumEntries();
160 for (i = 0; i < count; i++) {
161 entry = zip->getEntryByIndex(i);
162 if (bundle->getVerbose()) {
163 char dateBuf[32];
164 time_t when;
165
166 when = entry->getModWhen();
167 strftime(dateBuf, sizeof(dateBuf), "%m-%d-%y %H:%M",
168 localtime(&when));
169
170 printf("%8ld %-7.7s %7ld %3d%% %8zd %s %08lx %s\n",
171 (long) entry->getUncompressedLen(),
172 compressionName(entry->getCompressionMethod()),
173 (long) entry->getCompressedLen(),
174 calcPercent(entry->getUncompressedLen(),
175 entry->getCompressedLen()),
176 (size_t) entry->getLFHOffset(),
177 dateBuf,
178 entry->getCRC32(),
179 entry->getFileName());
180 } else {
181 printf("%s\n", entry->getFileName());
182 }
183
184 totalUncLen += entry->getUncompressedLen();
185 totalCompLen += entry->getCompressedLen();
186 }
187
188 if (bundle->getVerbose()) {
189 printf(
190 "-------- ------- --- -------\n");
191 printf("%8ld %7ld %2d%% %d files\n",
192 totalUncLen,
193 totalCompLen,
194 calcPercent(totalUncLen, totalCompLen),
195 zip->getNumEntries());
196 }
197
198 if (bundle->getAndroidList()) {
199 AssetManager assets;
200 if (!assets.addAssetPath(String8(zipFileName), NULL)) {
201 fprintf(stderr, "ERROR: list -a failed because assets could not be loaded\n");
202 goto bail;
203 }
204
205 const ResTable& res = assets.getResources(false);
206 if (&res == NULL) {
207 printf("\nNo resource table found.\n");
208 } else {
209#ifndef HAVE_ANDROID_OS
210 printf("\nResource table:\n");
211 res.print(false);
212#endif
213 }
214
215 Asset* manifestAsset = assets.openNonAsset("AndroidManifest.xml",
216 Asset::ACCESS_BUFFER);
217 if (manifestAsset == NULL) {
218 printf("\nNo AndroidManifest.xml found.\n");
219 } else {
220 printf("\nAndroid manifest:\n");
221 ResXMLTree tree;
222 tree.setTo(manifestAsset->getBuffer(true),
223 manifestAsset->getLength());
224 printXMLBlock(&tree);
225 }
226 delete manifestAsset;
227 }
228
229 result = 0;
230
231bail:
232 delete zip;
233 return result;
234}
235
236static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes)
237{
238 size_t N = tree.getAttributeCount();
239 for (size_t i=0; i<N; i++) {
240 if (tree.getAttributeNameResID(i) == attrRes) {
241 return (ssize_t)i;
242 }
243 }
244 return -1;
245}
246
247String8 getAttribute(const ResXMLTree& tree, const char* ns,
248 const char* attr, String8* outError)
249{
250 ssize_t idx = tree.indexOfAttribute(ns, attr);
251 if (idx < 0) {
252 return String8();
253 }
254 Res_value value;
255 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
256 if (value.dataType != Res_value::TYPE_STRING) {
257 if (outError != NULL) {
258 *outError = "attribute is not a string value";
259 }
260 return String8();
261 }
262 }
263 size_t len;
264 const uint16_t* str = tree.getAttributeStringValue(idx, &len);
265 return str ? String8(str, len) : String8();
266}
267
268static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError)
269{
270 ssize_t idx = indexOfAttribute(tree, attrRes);
271 if (idx < 0) {
272 return String8();
273 }
274 Res_value value;
275 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
276 if (value.dataType != Res_value::TYPE_STRING) {
277 if (outError != NULL) {
278 *outError = "attribute is not a string value";
279 }
280 return String8();
281 }
282 }
283 size_t len;
284 const uint16_t* str = tree.getAttributeStringValue(idx, &len);
285 return str ? String8(str, len) : String8();
286}
287
288static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes,
289 String8* outError, int32_t defValue = -1)
290{
291 ssize_t idx = indexOfAttribute(tree, attrRes);
292 if (idx < 0) {
293 return defValue;
294 }
295 Res_value value;
296 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
297 if (value.dataType < Res_value::TYPE_FIRST_INT
298 || value.dataType > Res_value::TYPE_LAST_INT) {
299 if (outError != NULL) {
300 *outError = "attribute is not an integer value";
301 }
302 return defValue;
303 }
304 }
305 return value.data;
306}
307
308static int32_t getResolvedIntegerAttribute(const ResTable* resTable, const ResXMLTree& tree,
309 uint32_t attrRes, String8* outError, int32_t defValue = -1)
310{
311 ssize_t idx = indexOfAttribute(tree, attrRes);
312 if (idx < 0) {
313 return defValue;
314 }
315 Res_value value;
316 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
317 if (value.dataType == Res_value::TYPE_REFERENCE) {
318 resTable->resolveReference(&value, 0);
319 }
320 if (value.dataType < Res_value::TYPE_FIRST_INT
321 || value.dataType > Res_value::TYPE_LAST_INT) {
322 if (outError != NULL) {
323 *outError = "attribute is not an integer value";
324 }
325 return defValue;
326 }
327 }
328 return value.data;
329}
330
331static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree,
332 uint32_t attrRes, String8* outError)
333{
334 ssize_t idx = indexOfAttribute(tree, attrRes);
335 if (idx < 0) {
336 return String8();
337 }
338 Res_value value;
339 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
340 if (value.dataType == Res_value::TYPE_STRING) {
341 size_t len;
342 const uint16_t* str = tree.getAttributeStringValue(idx, &len);
343 return str ? String8(str, len) : String8();
344 }
345 resTable->resolveReference(&value, 0);
346 if (value.dataType != Res_value::TYPE_STRING) {
347 if (outError != NULL) {
348 *outError = "attribute is not a string value";
349 }
350 return String8();
351 }
352 }
353 size_t len;
354 const Res_value* value2 = &value;
355 const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len);
356 return str ? String8(str, len) : String8();
357}
358
359static void getResolvedResourceAttribute(Res_value* value, const ResTable* resTable,
360 const ResXMLTree& tree, uint32_t attrRes, String8* outError)
361{
362 ssize_t idx = indexOfAttribute(tree, attrRes);
363 if (idx < 0) {
364 if (outError != NULL) {
365 *outError = "attribute could not be found";
366 }
367 return;
368 }
369 if (tree.getAttributeValue(idx, value) != NO_ERROR) {
370 if (value->dataType == Res_value::TYPE_REFERENCE) {
371 resTable->resolveReference(value, 0);
372 }
373 // The attribute was found and was resolved if need be.
374 return;
375 }
376 if (outError != NULL) {
377 *outError = "error getting resolved resource attribute";
378 }
379}
380
Maurice Chu76327312013-10-16 18:28:46 -0700381static void printResolvedResourceAttribute(const ResTable* resTable, const ResXMLTree& tree,
382 uint32_t attrRes, String8 attrLabel, String8* outError)
383{
384 Res_value value;
385 getResolvedResourceAttribute(&value, resTable, tree, attrRes, outError);
386 if (*outError != "") {
387 *outError = "error print resolved resource attribute";
388 return;
389 }
390 if (value.dataType == Res_value::TYPE_STRING) {
391 String8 result = getResolvedAttribute(resTable, tree, attrRes, outError);
392 printf("%s='%s'", attrLabel.string(), result.string());
393 } else if (Res_value::TYPE_FIRST_INT <= value.dataType &&
394 value.dataType <= Res_value::TYPE_LAST_INT) {
395 printf("%s='%d'", attrLabel.string(), value.data);
396 } else {
397 printf("%s='0x%x'", attrLabel.string(), (int)value.data);
398 }
399}
400
Adam Lesinski282e1812014-01-23 18:17:42 -0800401// These are attribute resource constants for the platform, as found
402// in android.R.attr
403enum {
404 LABEL_ATTR = 0x01010001,
405 ICON_ATTR = 0x01010002,
406 NAME_ATTR = 0x01010003,
Adam Lesinskia5018c92013-09-30 16:23:15 -0700407 PERMISSION_ATTR = 0x01010006,
Adam Lesinski94fc9122013-09-30 17:16:09 -0700408 RESOURCE_ATTR = 0x01010025,
Adam Lesinski282e1812014-01-23 18:17:42 -0800409 DEBUGGABLE_ATTR = 0x0101000f,
410 VALUE_ATTR = 0x01010024,
411 VERSION_CODE_ATTR = 0x0101021b,
412 VERSION_NAME_ATTR = 0x0101021c,
413 SCREEN_ORIENTATION_ATTR = 0x0101001e,
414 MIN_SDK_VERSION_ATTR = 0x0101020c,
415 MAX_SDK_VERSION_ATTR = 0x01010271,
416 REQ_TOUCH_SCREEN_ATTR = 0x01010227,
417 REQ_KEYBOARD_TYPE_ATTR = 0x01010228,
418 REQ_HARD_KEYBOARD_ATTR = 0x01010229,
419 REQ_NAVIGATION_ATTR = 0x0101022a,
420 REQ_FIVE_WAY_NAV_ATTR = 0x01010232,
421 TARGET_SDK_VERSION_ATTR = 0x01010270,
422 TEST_ONLY_ATTR = 0x01010272,
423 ANY_DENSITY_ATTR = 0x0101026c,
424 GL_ES_VERSION_ATTR = 0x01010281,
425 SMALL_SCREEN_ATTR = 0x01010284,
426 NORMAL_SCREEN_ATTR = 0x01010285,
427 LARGE_SCREEN_ATTR = 0x01010286,
428 XLARGE_SCREEN_ATTR = 0x010102bf,
429 REQUIRED_ATTR = 0x0101028e,
430 SCREEN_SIZE_ATTR = 0x010102ca,
431 SCREEN_DENSITY_ATTR = 0x010102cb,
432 REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364,
433 COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365,
434 LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366,
435 PUBLIC_KEY_ATTR = 0x010103a6,
Adam Lesinski94fc9122013-09-30 17:16:09 -0700436 CATEGORY_ATTR = 0x010103e8,
Adam Lesinski282e1812014-01-23 18:17:42 -0800437};
438
439const char *getComponentName(String8 &pkgName, String8 &componentName) {
440 ssize_t idx = componentName.find(".");
441 String8 retStr(pkgName);
442 if (idx == 0) {
443 retStr += componentName;
444 } else if (idx < 0) {
445 retStr += ".";
446 retStr += componentName;
447 } else {
448 return componentName.string();
449 }
450 return retStr.string();
451}
452
453static void printCompatibleScreens(ResXMLTree& tree) {
454 size_t len;
455 ResXMLTree::event_code_t code;
456 int depth = 0;
457 bool first = true;
458 printf("compatible-screens:");
459 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
460 if (code == ResXMLTree::END_TAG) {
461 depth--;
462 if (depth < 0) {
463 break;
464 }
465 continue;
466 }
467 if (code != ResXMLTree::START_TAG) {
468 continue;
469 }
470 depth++;
471 String8 tag(tree.getElementName(&len));
472 if (tag == "screen") {
473 int32_t screenSize = getIntegerAttribute(tree,
474 SCREEN_SIZE_ATTR, NULL, -1);
475 int32_t screenDensity = getIntegerAttribute(tree,
476 SCREEN_DENSITY_ATTR, NULL, -1);
477 if (screenSize > 0 && screenDensity > 0) {
478 if (!first) {
479 printf(",");
480 }
481 first = false;
482 printf("'%d/%d'", screenSize, screenDensity);
483 }
484 }
485 }
486 printf("\n");
487}
488
Adam Lesinski94fc9122013-09-30 17:16:09 -0700489Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool offHost,
490 String8 *outError = NULL)
491{
492 Asset* aidAsset = assets.openNonAsset(xmlPath, Asset::ACCESS_BUFFER);
493 if (aidAsset == NULL) {
494 if (outError != NULL) *outError = "xml resource does not exist";
495 return Vector<String8>();
496 }
497
498 const String8 serviceTagName(offHost ? "offhost-apdu-service" : "host-apdu-service");
499
500 bool withinApduService = false;
501 Vector<String8> categories;
502
503 String8 error;
504 ResXMLTree tree;
505 tree.setTo(aidAsset->getBuffer(true), aidAsset->getLength());
506
507 size_t len;
508 int depth = 0;
509 ResXMLTree::event_code_t code;
510 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
511 if (code == ResXMLTree::END_TAG) {
512 depth--;
513 String8 tag(tree.getElementName(&len));
514
515 if (depth == 0 && tag == serviceTagName) {
516 withinApduService = false;
517 }
518
519 } else if (code == ResXMLTree::START_TAG) {
520 depth++;
521 String8 tag(tree.getElementName(&len));
522
523 if (depth == 1) {
524 if (tag == serviceTagName) {
525 withinApduService = true;
526 }
527 } else if (depth == 2 && withinApduService) {
528 if (tag == "aid-group") {
529 String8 category = getAttribute(tree, CATEGORY_ATTR, &error);
530 if (error != "") {
531 if (outError != NULL) *outError = error;
532 return Vector<String8>();
533 }
534
535 categories.add(category);
536 }
537 }
538 }
539 }
540 aidAsset->close();
541 return categories;
542}
543
Adam Lesinski282e1812014-01-23 18:17:42 -0800544/*
545 * Handle the "dump" command, to extract select data from an archive.
546 */
547extern char CONSOLE_DATA[2925]; // see EOF
548int doDump(Bundle* bundle)
549{
550 status_t result = UNKNOWN_ERROR;
551 Asset* asset = NULL;
552
553 if (bundle->getFileSpecCount() < 1) {
554 fprintf(stderr, "ERROR: no dump option specified\n");
555 return 1;
556 }
557
558 if (bundle->getFileSpecCount() < 2) {
559 fprintf(stderr, "ERROR: no dump file specified\n");
560 return 1;
561 }
562
563 const char* option = bundle->getFileSpecEntry(0);
564 const char* filename = bundle->getFileSpecEntry(1);
565
566 AssetManager assets;
567 void* assetsCookie;
568 if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
569 fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
570 return 1;
571 }
572
573 // Make a dummy config for retrieving resources... we need to supply
574 // non-default values for some configs so that we can retrieve resources
575 // in the app that don't have a default. The most important of these is
576 // the API version because key resources like icons will have an implicit
577 // version if they are using newer config types like density.
578 ResTable_config config;
579 config.language[0] = 'e';
580 config.language[1] = 'n';
581 config.country[0] = 'U';
582 config.country[1] = 'S';
583 config.orientation = ResTable_config::ORIENTATION_PORT;
584 config.density = ResTable_config::DENSITY_MEDIUM;
585 config.sdkVersion = 10000; // Very high.
586 config.screenWidthDp = 320;
587 config.screenHeightDp = 480;
588 config.smallestScreenWidthDp = 320;
589 assets.setConfiguration(config);
590
591 const ResTable& res = assets.getResources(false);
592 if (&res == NULL) {
593 fprintf(stderr, "ERROR: dump failed because no resource table was found\n");
594 goto bail;
595 }
596
597 if (strcmp("resources", option) == 0) {
598#ifndef HAVE_ANDROID_OS
599 res.print(bundle->getValues());
600#endif
601
602 } else if (strcmp("strings", option) == 0) {
603 const ResStringPool* pool = res.getTableStringBlock(0);
604 printStringPool(pool);
605
606 } else if (strcmp("xmltree", option) == 0) {
607 if (bundle->getFileSpecCount() < 3) {
608 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
609 goto bail;
610 }
611
612 for (int i=2; i<bundle->getFileSpecCount(); i++) {
613 const char* resname = bundle->getFileSpecEntry(i);
614 ResXMLTree tree;
615 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
616 if (asset == NULL) {
617 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
618 goto bail;
619 }
620
621 if (tree.setTo(asset->getBuffer(true),
622 asset->getLength()) != NO_ERROR) {
623 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
624 goto bail;
625 }
626 tree.restart();
627 printXMLBlock(&tree);
628 tree.uninit();
629 delete asset;
630 asset = NULL;
631 }
632
633 } else if (strcmp("xmlstrings", option) == 0) {
634 if (bundle->getFileSpecCount() < 3) {
635 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
636 goto bail;
637 }
638
639 for (int i=2; i<bundle->getFileSpecCount(); i++) {
640 const char* resname = bundle->getFileSpecEntry(i);
641 ResXMLTree tree;
642 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
643 if (asset == NULL) {
644 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
645 goto bail;
646 }
647
648 if (tree.setTo(asset->getBuffer(true),
649 asset->getLength()) != NO_ERROR) {
650 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
651 goto bail;
652 }
653 printStringPool(&tree.getStrings());
654 delete asset;
655 asset = NULL;
656 }
657
658 } else {
659 ResXMLTree tree;
660 asset = assets.openNonAsset("AndroidManifest.xml",
661 Asset::ACCESS_BUFFER);
662 if (asset == NULL) {
663 fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
664 goto bail;
665 }
666
667 if (tree.setTo(asset->getBuffer(true),
668 asset->getLength()) != NO_ERROR) {
669 fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
670 goto bail;
671 }
672 tree.restart();
673
674 if (strcmp("permissions", option) == 0) {
675 size_t len;
676 ResXMLTree::event_code_t code;
677 int depth = 0;
678 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
679 if (code == ResXMLTree::END_TAG) {
680 depth--;
681 continue;
682 }
683 if (code != ResXMLTree::START_TAG) {
684 continue;
685 }
686 depth++;
687 String8 tag(tree.getElementName(&len));
688 //printf("Depth %d tag %s\n", depth, tag.string());
689 if (depth == 1) {
690 if (tag != "manifest") {
691 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
692 goto bail;
693 }
694 String8 pkg = getAttribute(tree, NULL, "package", NULL);
695 printf("package: %s\n", pkg.string());
696 } else if (depth == 2 && tag == "permission") {
697 String8 error;
698 String8 name = getAttribute(tree, NAME_ATTR, &error);
699 if (error != "") {
700 fprintf(stderr, "ERROR: %s\n", error.string());
701 goto bail;
702 }
703 printf("permission: %s\n", name.string());
704 } else if (depth == 2 && tag == "uses-permission") {
705 String8 error;
706 String8 name = getAttribute(tree, NAME_ATTR, &error);
707 if (error != "") {
708 fprintf(stderr, "ERROR: %s\n", error.string());
709 goto bail;
710 }
711 printf("uses-permission: %s\n", name.string());
712 int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
713 if (!req) {
714 printf("optional-permission: %s\n", name.string());
715 }
716 }
717 }
718 } else if (strcmp("badging", option) == 0) {
719 Vector<String8> locales;
720 res.getLocales(&locales);
721
722 Vector<ResTable_config> configs;
723 res.getConfigurations(&configs);
724 SortedVector<int> densities;
725 const size_t NC = configs.size();
726 for (size_t i=0; i<NC; i++) {
727 int dens = configs[i].density;
728 if (dens == 0) {
729 dens = 160;
730 }
731 densities.add(dens);
732 }
733
734 size_t len;
735 ResXMLTree::event_code_t code;
736 int depth = 0;
737 String8 error;
738 bool withinActivity = false;
739 bool isMainActivity = false;
740 bool isLauncherActivity = false;
741 bool isSearchable = false;
742 bool withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700743 bool withinSupportsInput = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800744 bool withinReceiver = false;
745 bool withinService = false;
746 bool withinIntentFilter = false;
747 bool hasMainActivity = false;
748 bool hasOtherActivities = false;
749 bool hasOtherReceivers = false;
750 bool hasOtherServices = false;
751 bool hasWallpaperService = false;
752 bool hasImeService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700753 bool hasAccessibilityService = false;
754 bool hasPrintService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800755 bool hasWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700756 bool hasDeviceAdminReceiver = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800757 bool hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700758 bool hasPaymentService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800759 bool actMainActivity = false;
760 bool actWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700761 bool actDeviceAdminEnabled = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800762 bool actImeService = false;
763 bool actWallpaperService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700764 bool actAccessibilityService = false;
765 bool actPrintService = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700766 bool actHostApduService = false;
767 bool actOffHostApduService = false;
768 bool hasMetaHostPaymentCategory = false;
769 bool hasMetaOffHostPaymentCategory = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700770
771 // These permissions are required by services implementing services
772 // the system binds to (IME, Accessibility, PrintServices, etc.)
773 bool hasBindDeviceAdminPermission = false;
774 bool hasBindInputMethodPermission = false;
775 bool hasBindAccessibilityServicePermission = false;
776 bool hasBindPrintServicePermission = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700777 bool hasBindNfcServicePermission = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800778
779 // These two implement the implicit permissions that are granted
780 // to pre-1.6 applications.
781 bool hasWriteExternalStoragePermission = false;
782 bool hasReadPhoneStatePermission = false;
783
784 // If an app requests write storage, they will also get read storage.
785 bool hasReadExternalStoragePermission = false;
786
787 // Implement transition to read and write call log.
788 bool hasReadContactsPermission = false;
789 bool hasWriteContactsPermission = false;
790 bool hasReadCallLogPermission = false;
791 bool hasWriteCallLogPermission = false;
792
793 // This next group of variables is used to implement a group of
794 // backward-compatibility heuristics necessitated by the addition of
795 // some new uses-feature constants in 2.1 and 2.2. In most cases, the
796 // heuristic is "if an app requests a permission but doesn't explicitly
797 // request the corresponding <uses-feature>, presume it's there anyway".
798 bool specCameraFeature = false; // camera-related
799 bool specCameraAutofocusFeature = false;
800 bool reqCameraAutofocusFeature = false;
801 bool reqCameraFlashFeature = false;
802 bool hasCameraPermission = false;
803 bool specLocationFeature = false; // location-related
804 bool specNetworkLocFeature = false;
805 bool reqNetworkLocFeature = false;
806 bool specGpsFeature = false;
807 bool reqGpsFeature = false;
808 bool hasMockLocPermission = false;
809 bool hasCoarseLocPermission = false;
810 bool hasGpsPermission = false;
811 bool hasGeneralLocPermission = false;
812 bool specBluetoothFeature = false; // Bluetooth API-related
813 bool hasBluetoothPermission = false;
814 bool specMicrophoneFeature = false; // microphone-related
815 bool hasRecordAudioPermission = false;
816 bool specWiFiFeature = false;
817 bool hasWiFiPermission = false;
818 bool specTelephonyFeature = false; // telephony-related
819 bool reqTelephonySubFeature = false;
820 bool hasTelephonyPermission = false;
821 bool specTouchscreenFeature = false; // touchscreen-related
822 bool specMultitouchFeature = false;
823 bool reqDistinctMultitouchFeature = false;
824 bool specScreenPortraitFeature = false;
825 bool specScreenLandscapeFeature = false;
826 bool reqScreenPortraitFeature = false;
827 bool reqScreenLandscapeFeature = false;
828 // 2.2 also added some other features that apps can request, but that
829 // have no corresponding permission, so we cannot implement any
830 // back-compatibility heuristic for them. The below are thus unnecessary
831 // (but are retained here for documentary purposes.)
832 //bool specCompassFeature = false;
833 //bool specAccelerometerFeature = false;
834 //bool specProximityFeature = false;
835 //bool specAmbientLightFeature = false;
836 //bool specLiveWallpaperFeature = false;
837
838 int targetSdk = 0;
839 int smallScreen = 1;
840 int normalScreen = 1;
841 int largeScreen = 1;
842 int xlargeScreen = 1;
843 int anyDensity = 1;
844 int requiresSmallestWidthDp = 0;
845 int compatibleWidthLimitDp = 0;
846 int largestWidthLimitDp = 0;
847 String8 pkg;
848 String8 activityName;
849 String8 activityLabel;
850 String8 activityIcon;
851 String8 receiverName;
852 String8 serviceName;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700853 Vector<String8> supportedInput;
Adam Lesinski282e1812014-01-23 18:17:42 -0800854 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
855 if (code == ResXMLTree::END_TAG) {
856 depth--;
857 if (depth < 2) {
Michael Wrightec4fdec2013-09-06 16:50:52 -0700858 if (withinSupportsInput && !supportedInput.isEmpty()) {
859 printf("supports-input: '");
860 const size_t N = supportedInput.size();
861 for (size_t i=0; i<N; i++) {
862 printf("%s", supportedInput[i].string());
863 if (i != N - 1) {
864 printf("' '");
865 } else {
866 printf("'\n");
867 }
868 }
869 supportedInput.clear();
870 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800871 withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700872 withinSupportsInput = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800873 } else if (depth < 3) {
874 if (withinActivity && isMainActivity && isLauncherActivity) {
875 const char *aName = getComponentName(pkg, activityName);
876 printf("launchable-activity:");
877 if (aName != NULL) {
878 printf(" name='%s' ", aName);
879 }
880 printf(" label='%s' icon='%s'\n",
881 activityLabel.string(),
882 activityIcon.string());
883 }
884 if (!hasIntentFilter) {
885 hasOtherActivities |= withinActivity;
886 hasOtherReceivers |= withinReceiver;
887 hasOtherServices |= withinService;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700888 } else {
889 if (withinService) {
890 hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
891 hasBindNfcServicePermission);
892 hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
893 hasBindNfcServicePermission);
894 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800895 }
896 withinActivity = false;
897 withinService = false;
898 withinReceiver = false;
899 hasIntentFilter = false;
900 isMainActivity = isLauncherActivity = false;
901 } else if (depth < 4) {
902 if (withinIntentFilter) {
903 if (withinActivity) {
904 hasMainActivity |= actMainActivity;
905 hasOtherActivities |= !actMainActivity;
906 } else if (withinReceiver) {
907 hasWidgetReceivers |= actWidgetReceivers;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700908 hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
909 hasBindDeviceAdminPermission);
910 hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
Adam Lesinski282e1812014-01-23 18:17:42 -0800911 } else if (withinService) {
912 hasImeService |= actImeService;
913 hasWallpaperService |= actWallpaperService;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700914 hasAccessibilityService |= (actAccessibilityService &&
915 hasBindAccessibilityServicePermission);
916 hasPrintService |= (actPrintService && hasBindPrintServicePermission);
917 hasOtherServices |= (!actImeService && !actWallpaperService &&
Adam Lesinski94fc9122013-09-30 17:16:09 -0700918 !actAccessibilityService && !actPrintService &&
919 !actHostApduService && !actOffHostApduService);
Adam Lesinski282e1812014-01-23 18:17:42 -0800920 }
921 }
922 withinIntentFilter = false;
923 }
924 continue;
925 }
926 if (code != ResXMLTree::START_TAG) {
927 continue;
928 }
929 depth++;
930 String8 tag(tree.getElementName(&len));
931 //printf("Depth %d, %s\n", depth, tag.string());
932 if (depth == 1) {
933 if (tag != "manifest") {
934 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
935 goto bail;
936 }
937 pkg = getAttribute(tree, NULL, "package", NULL);
938 printf("package: name='%s' ", pkg.string());
939 int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
940 if (error != "") {
941 fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
942 goto bail;
943 }
944 if (versionCode > 0) {
945 printf("versionCode='%d' ", versionCode);
946 } else {
947 printf("versionCode='' ");
948 }
949 String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
950 if (error != "") {
951 fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
952 goto bail;
953 }
954 printf("versionName='%s'\n", versionName.string());
955 } else if (depth == 2) {
956 withinApplication = false;
957 if (tag == "application") {
958 withinApplication = true;
959
960 String8 label;
961 const size_t NL = locales.size();
962 for (size_t i=0; i<NL; i++) {
963 const char* localeStr = locales[i].string();
964 assets.setLocale(localeStr != NULL ? localeStr : "");
965 String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
966 if (llabel != "") {
967 if (localeStr == NULL || strlen(localeStr) == 0) {
968 label = llabel;
969 printf("application-label:'%s'\n", llabel.string());
970 } else {
971 if (label == "") {
972 label = llabel;
973 }
974 printf("application-label-%s:'%s'\n", localeStr,
975 llabel.string());
976 }
977 }
978 }
979
980 ResTable_config tmpConfig = config;
981 const size_t ND = densities.size();
982 for (size_t i=0; i<ND; i++) {
983 tmpConfig.density = densities[i];
984 assets.setConfiguration(tmpConfig);
985 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
986 if (icon != "") {
987 printf("application-icon-%d:'%s'\n", densities[i], icon.string());
988 }
989 }
990 assets.setConfiguration(config);
991
992 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
993 if (error != "") {
994 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
995 goto bail;
996 }
997 int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
998 if (error != "") {
999 fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
1000 goto bail;
1001 }
1002 printf("application: label='%s' ", label.string());
1003 printf("icon='%s'\n", icon.string());
1004 if (testOnly != 0) {
1005 printf("testOnly='%d'\n", testOnly);
1006 }
1007
1008 int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
1009 if (error != "") {
1010 fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
1011 goto bail;
1012 }
1013 if (debuggable != 0) {
1014 printf("application-debuggable\n");
1015 }
1016 } else if (tag == "uses-sdk") {
1017 int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
1018 if (error != "") {
1019 error = "";
1020 String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
1021 if (error != "") {
1022 fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
1023 error.string());
1024 goto bail;
1025 }
1026 if (name == "Donut") targetSdk = 4;
1027 printf("sdkVersion:'%s'\n", name.string());
1028 } else if (code != -1) {
1029 targetSdk = code;
1030 printf("sdkVersion:'%d'\n", code);
1031 }
1032 code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
1033 if (code != -1) {
1034 printf("maxSdkVersion:'%d'\n", code);
1035 }
1036 code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
1037 if (error != "") {
1038 error = "";
1039 String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
1040 if (error != "") {
1041 fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
1042 error.string());
1043 goto bail;
1044 }
1045 if (name == "Donut" && targetSdk < 4) targetSdk = 4;
1046 printf("targetSdkVersion:'%s'\n", name.string());
1047 } else if (code != -1) {
1048 if (targetSdk < code) {
1049 targetSdk = code;
1050 }
1051 printf("targetSdkVersion:'%d'\n", code);
1052 }
1053 } else if (tag == "uses-configuration") {
1054 int32_t reqTouchScreen = getIntegerAttribute(tree,
1055 REQ_TOUCH_SCREEN_ATTR, NULL, 0);
1056 int32_t reqKeyboardType = getIntegerAttribute(tree,
1057 REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
1058 int32_t reqHardKeyboard = getIntegerAttribute(tree,
1059 REQ_HARD_KEYBOARD_ATTR, NULL, 0);
1060 int32_t reqNavigation = getIntegerAttribute(tree,
1061 REQ_NAVIGATION_ATTR, NULL, 0);
1062 int32_t reqFiveWayNav = getIntegerAttribute(tree,
1063 REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
1064 printf("uses-configuration:");
1065 if (reqTouchScreen != 0) {
1066 printf(" reqTouchScreen='%d'", reqTouchScreen);
1067 }
1068 if (reqKeyboardType != 0) {
1069 printf(" reqKeyboardType='%d'", reqKeyboardType);
1070 }
1071 if (reqHardKeyboard != 0) {
1072 printf(" reqHardKeyboard='%d'", reqHardKeyboard);
1073 }
1074 if (reqNavigation != 0) {
1075 printf(" reqNavigation='%d'", reqNavigation);
1076 }
1077 if (reqFiveWayNav != 0) {
1078 printf(" reqFiveWayNav='%d'", reqFiveWayNav);
1079 }
1080 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001081 } else if (tag == "supports-input") {
1082 withinSupportsInput = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001083 } else if (tag == "supports-screens") {
1084 smallScreen = getIntegerAttribute(tree,
1085 SMALL_SCREEN_ATTR, NULL, 1);
1086 normalScreen = getIntegerAttribute(tree,
1087 NORMAL_SCREEN_ATTR, NULL, 1);
1088 largeScreen = getIntegerAttribute(tree,
1089 LARGE_SCREEN_ATTR, NULL, 1);
1090 xlargeScreen = getIntegerAttribute(tree,
1091 XLARGE_SCREEN_ATTR, NULL, 1);
1092 anyDensity = getIntegerAttribute(tree,
1093 ANY_DENSITY_ATTR, NULL, 1);
1094 requiresSmallestWidthDp = getIntegerAttribute(tree,
1095 REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
1096 compatibleWidthLimitDp = getIntegerAttribute(tree,
1097 COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1098 largestWidthLimitDp = getIntegerAttribute(tree,
1099 LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1100 } else if (tag == "uses-feature") {
1101 String8 name = getAttribute(tree, NAME_ATTR, &error);
1102
1103 if (name != "" && error == "") {
1104 int req = getIntegerAttribute(tree,
1105 REQUIRED_ATTR, NULL, 1);
1106
1107 if (name == "android.hardware.camera") {
1108 specCameraFeature = true;
1109 } else if (name == "android.hardware.camera.autofocus") {
1110 // these have no corresponding permission to check for,
1111 // but should imply the foundational camera permission
1112 reqCameraAutofocusFeature = reqCameraAutofocusFeature || req;
1113 specCameraAutofocusFeature = true;
1114 } else if (req && (name == "android.hardware.camera.flash")) {
1115 // these have no corresponding permission to check for,
1116 // but should imply the foundational camera permission
1117 reqCameraFlashFeature = true;
1118 } else if (name == "android.hardware.location") {
1119 specLocationFeature = true;
1120 } else if (name == "android.hardware.location.network") {
1121 specNetworkLocFeature = true;
1122 reqNetworkLocFeature = reqNetworkLocFeature || req;
1123 } else if (name == "android.hardware.location.gps") {
1124 specGpsFeature = true;
1125 reqGpsFeature = reqGpsFeature || req;
1126 } else if (name == "android.hardware.bluetooth") {
1127 specBluetoothFeature = true;
1128 } else if (name == "android.hardware.touchscreen") {
1129 specTouchscreenFeature = true;
1130 } else if (name == "android.hardware.touchscreen.multitouch") {
1131 specMultitouchFeature = true;
1132 } else if (name == "android.hardware.touchscreen.multitouch.distinct") {
1133 reqDistinctMultitouchFeature = reqDistinctMultitouchFeature || req;
1134 } else if (name == "android.hardware.microphone") {
1135 specMicrophoneFeature = true;
1136 } else if (name == "android.hardware.wifi") {
1137 specWiFiFeature = true;
1138 } else if (name == "android.hardware.telephony") {
1139 specTelephonyFeature = true;
1140 } else if (req && (name == "android.hardware.telephony.gsm" ||
1141 name == "android.hardware.telephony.cdma")) {
1142 // these have no corresponding permission to check for,
1143 // but should imply the foundational telephony permission
1144 reqTelephonySubFeature = true;
1145 } else if (name == "android.hardware.screen.portrait") {
1146 specScreenPortraitFeature = true;
1147 } else if (name == "android.hardware.screen.landscape") {
1148 specScreenLandscapeFeature = true;
1149 }
1150 printf("uses-feature%s:'%s'\n",
1151 req ? "" : "-not-required", name.string());
1152 } else {
1153 int vers = getIntegerAttribute(tree,
1154 GL_ES_VERSION_ATTR, &error);
1155 if (error == "") {
1156 printf("uses-gl-es:'0x%x'\n", vers);
1157 }
1158 }
1159 } else if (tag == "uses-permission") {
1160 String8 name = getAttribute(tree, NAME_ATTR, &error);
1161 if (name != "" && error == "") {
1162 if (name == "android.permission.CAMERA") {
1163 hasCameraPermission = true;
1164 } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
1165 hasGpsPermission = true;
1166 } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
1167 hasMockLocPermission = true;
1168 } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
1169 hasCoarseLocPermission = true;
1170 } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
1171 name == "android.permission.INSTALL_LOCATION_PROVIDER") {
1172 hasGeneralLocPermission = true;
1173 } else if (name == "android.permission.BLUETOOTH" ||
1174 name == "android.permission.BLUETOOTH_ADMIN") {
1175 hasBluetoothPermission = true;
1176 } else if (name == "android.permission.RECORD_AUDIO") {
1177 hasRecordAudioPermission = true;
1178 } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
1179 name == "android.permission.CHANGE_WIFI_STATE" ||
1180 name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
1181 hasWiFiPermission = true;
1182 } else if (name == "android.permission.CALL_PHONE" ||
1183 name == "android.permission.CALL_PRIVILEGED" ||
1184 name == "android.permission.MODIFY_PHONE_STATE" ||
1185 name == "android.permission.PROCESS_OUTGOING_CALLS" ||
1186 name == "android.permission.READ_SMS" ||
1187 name == "android.permission.RECEIVE_SMS" ||
1188 name == "android.permission.RECEIVE_MMS" ||
1189 name == "android.permission.RECEIVE_WAP_PUSH" ||
1190 name == "android.permission.SEND_SMS" ||
1191 name == "android.permission.WRITE_APN_SETTINGS" ||
1192 name == "android.permission.WRITE_SMS") {
1193 hasTelephonyPermission = true;
1194 } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
1195 hasWriteExternalStoragePermission = true;
1196 } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
1197 hasReadExternalStoragePermission = true;
1198 } else if (name == "android.permission.READ_PHONE_STATE") {
1199 hasReadPhoneStatePermission = true;
1200 } else if (name == "android.permission.READ_CONTACTS") {
1201 hasReadContactsPermission = true;
1202 } else if (name == "android.permission.WRITE_CONTACTS") {
1203 hasWriteContactsPermission = true;
1204 } else if (name == "android.permission.READ_CALL_LOG") {
1205 hasReadCallLogPermission = true;
1206 } else if (name == "android.permission.WRITE_CALL_LOG") {
1207 hasWriteCallLogPermission = true;
1208 }
1209 printf("uses-permission:'%s'\n", name.string());
1210 int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
1211 if (!req) {
1212 printf("optional-permission:'%s'\n", name.string());
1213 }
1214 } else {
1215 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1216 error.string());
1217 goto bail;
1218 }
1219 } else if (tag == "uses-package") {
1220 String8 name = getAttribute(tree, NAME_ATTR, &error);
1221 if (name != "" && error == "") {
1222 printf("uses-package:'%s'\n", name.string());
1223 } else {
1224 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1225 error.string());
1226 goto bail;
1227 }
1228 } else if (tag == "original-package") {
1229 String8 name = getAttribute(tree, NAME_ATTR, &error);
1230 if (name != "" && error == "") {
1231 printf("original-package:'%s'\n", name.string());
1232 } else {
1233 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1234 error.string());
1235 goto bail;
1236 }
1237 } else if (tag == "supports-gl-texture") {
1238 String8 name = getAttribute(tree, NAME_ATTR, &error);
1239 if (name != "" && error == "") {
1240 printf("supports-gl-texture:'%s'\n", name.string());
1241 } else {
1242 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1243 error.string());
1244 goto bail;
1245 }
1246 } else if (tag == "compatible-screens") {
1247 printCompatibleScreens(tree);
1248 depth--;
1249 } else if (tag == "package-verifier") {
1250 String8 name = getAttribute(tree, NAME_ATTR, &error);
1251 if (name != "" && error == "") {
1252 String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
1253 if (publicKey != "" && error == "") {
1254 printf("package-verifier: name='%s' publicKey='%s'\n",
1255 name.string(), publicKey.string());
1256 }
1257 }
1258 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001259 } else if (depth == 3) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001260 withinActivity = false;
1261 withinReceiver = false;
1262 withinService = false;
1263 hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001264 hasMetaHostPaymentCategory = false;
1265 hasMetaOffHostPaymentCategory = false;
1266 hasBindDeviceAdminPermission = false;
1267 hasBindInputMethodPermission = false;
1268 hasBindAccessibilityServicePermission = false;
1269 hasBindPrintServicePermission = false;
1270 hasBindNfcServicePermission = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001271 if (withinApplication) {
1272 if(tag == "activity") {
1273 withinActivity = true;
1274 activityName = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001275 if (error != "") {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001276 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1277 error.string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001278 goto bail;
1279 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001280
Michael Wrightec4fdec2013-09-06 16:50:52 -07001281 activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1282 if (error != "") {
1283 fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
1284 error.string());
1285 goto bail;
1286 }
1287
1288 activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1289 if (error != "") {
1290 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
1291 error.string());
1292 goto bail;
1293 }
1294
1295 int32_t orien = getResolvedIntegerAttribute(&res, tree,
1296 SCREEN_ORIENTATION_ATTR, &error);
1297 if (error == "") {
1298 if (orien == 0 || orien == 6 || orien == 8) {
1299 // Requests landscape, sensorLandscape, or reverseLandscape.
1300 reqScreenLandscapeFeature = true;
1301 } else if (orien == 1 || orien == 7 || orien == 9) {
1302 // Requests portrait, sensorPortrait, or reversePortrait.
1303 reqScreenPortraitFeature = true;
1304 }
1305 }
1306 } else if (tag == "uses-library") {
1307 String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
1308 if (error != "") {
1309 fprintf(stderr,
1310 "ERROR getting 'android:name' attribute for uses-library"
1311 " %s\n", error.string());
1312 goto bail;
1313 }
1314 int req = getIntegerAttribute(tree,
1315 REQUIRED_ATTR, NULL, 1);
1316 printf("uses-library%s:'%s'\n",
1317 req ? "" : "-not-required", libraryName.string());
1318 } else if (tag == "receiver") {
1319 withinReceiver = true;
1320 receiverName = getAttribute(tree, NAME_ATTR, &error);
1321
1322 if (error != "") {
1323 fprintf(stderr,
1324 "ERROR getting 'android:name' attribute for receiver:"
1325 " %s\n", error.string());
1326 goto bail;
1327 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001328
1329 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1330 if (error == "") {
1331 if (permission == "android.permission.BIND_DEVICE_ADMIN") {
1332 hasBindDeviceAdminPermission = true;
1333 }
1334 } else {
1335 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1336 " receiver '%s': %s\n", receiverName.string(), error.string());
1337 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001338 } else if (tag == "service") {
1339 withinService = true;
1340 serviceName = getAttribute(tree, NAME_ATTR, &error);
1341
1342 if (error != "") {
1343 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1344 "service:%s\n", error.string());
1345 goto bail;
1346 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001347
1348 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1349 if (error == "") {
1350 if (permission == "android.permission.BIND_INPUT_METHOD") {
1351 hasBindInputMethodPermission = true;
1352 } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
1353 hasBindAccessibilityServicePermission = true;
1354 } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
1355 hasBindPrintServicePermission = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001356 } else if (permission == "android.permission.BIND_NFC_SERVICE") {
1357 hasBindNfcServicePermission = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001358 }
1359 } else {
1360 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1361 " service '%s': %s\n", serviceName.string(), error.string());
1362 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001363 } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
1364 String8 metaDataName = getAttribute(tree, NAME_ATTR, &error);
1365 if (error != "") {
1366 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1367 "meta-data:%s\n", error.string());
1368 goto bail;
1369 }
1370 printf("meta-data: name='%s' ", metaDataName.string());
Maurice Chu76327312013-10-16 18:28:46 -07001371 printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"),
1372 &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001373 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001374 // Try looking for a RESOURCE_ATTR
1375 error = "";
1376 printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR,
1377 String8("resource"), &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001378 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001379 fprintf(stderr, "ERROR getting 'android:value' or "
1380 "'android:resource' attribute for "
1381 "meta-data:%s\n", error.string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001382 goto bail;
1383 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001384 }
Maurice Chu76327312013-10-16 18:28:46 -07001385 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001386 } else if (withinSupportsInput && tag == "input-type") {
1387 String8 name = getAttribute(tree, NAME_ATTR, &error);
1388 if (name != "" && error == "") {
1389 supportedInput.add(name);
1390 } else {
1391 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1392 error.string());
1393 goto bail;
1394 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001395 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001396 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001397 } else if (depth == 4) {
1398 if (tag == "intent-filter") {
1399 hasIntentFilter = true;
1400 withinIntentFilter = true;
1401 actMainActivity = false;
1402 actWidgetReceivers = false;
1403 actImeService = false;
1404 actWallpaperService = false;
1405 actAccessibilityService = false;
1406 actPrintService = false;
1407 actDeviceAdminEnabled = false;
1408 actHostApduService = false;
1409 actOffHostApduService = false;
1410 } else if (withinService && tag == "meta-data") {
1411 String8 name = getAttribute(tree, NAME_ATTR, &error);
1412 if (error != "") {
1413 fprintf(stderr, "ERROR getting 'android:name' attribute for"
1414 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1415 goto bail;
1416 }
1417
1418 if (name == "android.nfc.cardemulation.host_apdu_service" ||
1419 name == "android.nfc.cardemulation.off_host_apdu_service") {
1420 bool offHost = true;
1421 if (name == "android.nfc.cardemulation.host_apdu_service") {
1422 offHost = false;
1423 }
1424
1425 String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
1426 if (error != "") {
1427 fprintf(stderr, "ERROR getting 'android:resource' attribute for"
1428 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1429 goto bail;
1430 }
1431
1432 Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
1433 offHost, &error);
1434 if (error != "") {
1435 fprintf(stderr, "ERROR getting AID category for service '%s'\n",
1436 serviceName.string());
1437 goto bail;
1438 }
1439
1440 const size_t catLen = categories.size();
1441 for (size_t i = 0; i < catLen; i++) {
1442 bool paymentCategory = (categories[i] == "payment");
1443 if (offHost) {
1444 hasMetaOffHostPaymentCategory |= paymentCategory;
1445 } else {
1446 hasMetaHostPaymentCategory |= paymentCategory;
1447 }
1448 }
1449 }
1450 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001451 } else if ((depth == 5) && withinIntentFilter) {
1452 String8 action;
1453 if (tag == "action") {
1454 action = getAttribute(tree, NAME_ATTR, &error);
1455 if (error != "") {
1456 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1457 error.string());
1458 goto bail;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001459 }
1460
Adam Lesinskia5018c92013-09-30 16:23:15 -07001461 if (withinActivity) {
1462 if (action == "android.intent.action.MAIN") {
1463 isMainActivity = true;
1464 actMainActivity = true;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001465 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001466 } else if (withinReceiver) {
1467 if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
1468 actWidgetReceivers = true;
1469 } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
1470 actDeviceAdminEnabled = true;
1471 }
1472 } else if (withinService) {
1473 if (action == "android.view.InputMethod") {
1474 actImeService = true;
1475 } else if (action == "android.service.wallpaper.WallpaperService") {
1476 actWallpaperService = true;
1477 } else if (action == "android.accessibilityservice.AccessibilityService") {
1478 actAccessibilityService = true;
1479 } else if (action == "android.printservice.PrintService") {
1480 actPrintService = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001481 } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
1482 actHostApduService = true;
1483 } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
1484 actOffHostApduService = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001485 }
1486 }
1487 if (action == "android.intent.action.SEARCH") {
1488 isSearchable = true;
1489 }
1490 }
1491
1492 if (tag == "category") {
1493 String8 category = getAttribute(tree, NAME_ATTR, &error);
1494 if (error != "") {
1495 fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
1496 error.string());
1497 goto bail;
1498 }
1499 if (withinActivity) {
1500 if (category == "android.intent.category.LAUNCHER") {
1501 isLauncherActivity = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001502 }
1503 }
1504 }
1505 }
1506 }
1507
1508 // Pre-1.6 implicitly granted permission compatibility logic
1509 if (targetSdk < 4) {
1510 if (!hasWriteExternalStoragePermission) {
1511 printf("uses-permission:'android.permission.WRITE_EXTERNAL_STORAGE'\n");
1512 printf("uses-implied-permission:'android.permission.WRITE_EXTERNAL_STORAGE'," \
1513 "'targetSdkVersion < 4'\n");
1514 hasWriteExternalStoragePermission = true;
1515 }
1516 if (!hasReadPhoneStatePermission) {
1517 printf("uses-permission:'android.permission.READ_PHONE_STATE'\n");
1518 printf("uses-implied-permission:'android.permission.READ_PHONE_STATE'," \
1519 "'targetSdkVersion < 4'\n");
1520 }
1521 }
1522
1523 // If the application has requested WRITE_EXTERNAL_STORAGE, we will
1524 // force them to always take READ_EXTERNAL_STORAGE as well. We always
1525 // do this (regardless of target API version) because we can't have
1526 // an app with write permission but not read permission.
1527 if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
1528 printf("uses-permission:'android.permission.READ_EXTERNAL_STORAGE'\n");
1529 printf("uses-implied-permission:'android.permission.READ_EXTERNAL_STORAGE'," \
1530 "'requested WRITE_EXTERNAL_STORAGE'\n");
1531 }
1532
1533 // Pre-JellyBean call log permission compatibility.
1534 if (targetSdk < 16) {
1535 if (!hasReadCallLogPermission && hasReadContactsPermission) {
1536 printf("uses-permission:'android.permission.READ_CALL_LOG'\n");
1537 printf("uses-implied-permission:'android.permission.READ_CALL_LOG'," \
1538 "'targetSdkVersion < 16 and requested READ_CONTACTS'\n");
1539 }
1540 if (!hasWriteCallLogPermission && hasWriteContactsPermission) {
1541 printf("uses-permission:'android.permission.WRITE_CALL_LOG'\n");
1542 printf("uses-implied-permission:'android.permission.WRITE_CALL_LOG'," \
1543 "'targetSdkVersion < 16 and requested WRITE_CONTACTS'\n");
1544 }
1545 }
1546
1547 /* The following blocks handle printing "inferred" uses-features, based
1548 * on whether related features or permissions are used by the app.
1549 * Note that the various spec*Feature variables denote whether the
1550 * relevant tag was *present* in the AndroidManfest, not that it was
1551 * present and set to true.
1552 */
1553 // Camera-related back-compatibility logic
1554 if (!specCameraFeature) {
1555 if (reqCameraFlashFeature) {
1556 // if app requested a sub-feature (autofocus or flash) and didn't
1557 // request the base camera feature, we infer that it meant to
1558 printf("uses-feature:'android.hardware.camera'\n");
1559 printf("uses-implied-feature:'android.hardware.camera'," \
1560 "'requested android.hardware.camera.flash feature'\n");
1561 } else if (reqCameraAutofocusFeature) {
1562 // if app requested a sub-feature (autofocus or flash) and didn't
1563 // request the base camera feature, we infer that it meant to
1564 printf("uses-feature:'android.hardware.camera'\n");
1565 printf("uses-implied-feature:'android.hardware.camera'," \
1566 "'requested android.hardware.camera.autofocus feature'\n");
1567 } else if (hasCameraPermission) {
1568 // if app wants to use camera but didn't request the feature, we infer
1569 // that it meant to, and further that it wants autofocus
1570 // (which was the 1.0 - 1.5 behavior)
1571 printf("uses-feature:'android.hardware.camera'\n");
1572 if (!specCameraAutofocusFeature) {
1573 printf("uses-feature:'android.hardware.camera.autofocus'\n");
1574 printf("uses-implied-feature:'android.hardware.camera.autofocus'," \
1575 "'requested android.permission.CAMERA permission'\n");
1576 }
1577 }
1578 }
1579
1580 // Location-related back-compatibility logic
1581 if (!specLocationFeature &&
1582 (hasMockLocPermission || hasCoarseLocPermission || hasGpsPermission ||
1583 hasGeneralLocPermission || reqNetworkLocFeature || reqGpsFeature)) {
1584 // if app either takes a location-related permission or requests one of the
1585 // sub-features, we infer that it also meant to request the base location feature
1586 printf("uses-feature:'android.hardware.location'\n");
1587 printf("uses-implied-feature:'android.hardware.location'," \
1588 "'requested a location access permission'\n");
1589 }
1590 if (!specGpsFeature && hasGpsPermission) {
1591 // if app takes GPS (FINE location) perm but does not request the GPS
1592 // feature, we infer that it meant to
1593 printf("uses-feature:'android.hardware.location.gps'\n");
1594 printf("uses-implied-feature:'android.hardware.location.gps'," \
1595 "'requested android.permission.ACCESS_FINE_LOCATION permission'\n");
1596 }
1597 if (!specNetworkLocFeature && hasCoarseLocPermission) {
1598 // if app takes Network location (COARSE location) perm but does not request the
1599 // network location feature, we infer that it meant to
1600 printf("uses-feature:'android.hardware.location.network'\n");
1601 printf("uses-implied-feature:'android.hardware.location.network'," \
1602 "'requested android.permission.ACCESS_COARSE_LOCATION permission'\n");
1603 }
1604
1605 // Bluetooth-related compatibility logic
1606 if (!specBluetoothFeature && hasBluetoothPermission && (targetSdk > 4)) {
1607 // if app takes a Bluetooth permission but does not request the Bluetooth
1608 // feature, we infer that it meant to
1609 printf("uses-feature:'android.hardware.bluetooth'\n");
1610 printf("uses-implied-feature:'android.hardware.bluetooth'," \
1611 "'requested android.permission.BLUETOOTH or android.permission.BLUETOOTH_ADMIN " \
1612 "permission and targetSdkVersion > 4'\n");
1613 }
1614
1615 // Microphone-related compatibility logic
1616 if (!specMicrophoneFeature && hasRecordAudioPermission) {
1617 // if app takes the record-audio permission but does not request the microphone
1618 // feature, we infer that it meant to
1619 printf("uses-feature:'android.hardware.microphone'\n");
1620 printf("uses-implied-feature:'android.hardware.microphone'," \
1621 "'requested android.permission.RECORD_AUDIO permission'\n");
1622 }
1623
1624 // WiFi-related compatibility logic
1625 if (!specWiFiFeature && hasWiFiPermission) {
1626 // if app takes one of the WiFi permissions but does not request the WiFi
1627 // feature, we infer that it meant to
1628 printf("uses-feature:'android.hardware.wifi'\n");
1629 printf("uses-implied-feature:'android.hardware.wifi'," \
1630 "'requested android.permission.ACCESS_WIFI_STATE, " \
1631 "android.permission.CHANGE_WIFI_STATE, or " \
1632 "android.permission.CHANGE_WIFI_MULTICAST_STATE permission'\n");
1633 }
1634
1635 // Telephony-related compatibility logic
1636 if (!specTelephonyFeature && (hasTelephonyPermission || reqTelephonySubFeature)) {
1637 // if app takes one of the telephony permissions or requests a sub-feature but
1638 // does not request the base telephony feature, we infer that it meant to
1639 printf("uses-feature:'android.hardware.telephony'\n");
1640 printf("uses-implied-feature:'android.hardware.telephony'," \
1641 "'requested a telephony-related permission or feature'\n");
1642 }
1643
1644 // Touchscreen-related back-compatibility logic
1645 if (!specTouchscreenFeature) { // not a typo!
1646 // all apps are presumed to require a touchscreen, unless they explicitly say
1647 // <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
1648 // Note that specTouchscreenFeature is true if the tag is present, regardless
1649 // of whether its value is true or false, so this is safe
1650 printf("uses-feature:'android.hardware.touchscreen'\n");
1651 printf("uses-implied-feature:'android.hardware.touchscreen'," \
1652 "'assumed you require a touch screen unless explicitly made optional'\n");
1653 }
1654 if (!specMultitouchFeature && reqDistinctMultitouchFeature) {
1655 // if app takes one of the telephony permissions or requests a sub-feature but
1656 // does not request the base telephony feature, we infer that it meant to
1657 printf("uses-feature:'android.hardware.touchscreen.multitouch'\n");
1658 printf("uses-implied-feature:'android.hardware.touchscreen.multitouch'," \
1659 "'requested android.hardware.touchscreen.multitouch.distinct feature'\n");
1660 }
1661
1662 // Landscape/portrait-related compatibility logic
1663 if (!specScreenLandscapeFeature && !specScreenPortraitFeature) {
1664 // If the app has specified any activities in its manifest
1665 // that request a specific orientation, then assume that
1666 // orientation is required.
1667 if (reqScreenLandscapeFeature) {
1668 printf("uses-feature:'android.hardware.screen.landscape'\n");
1669 printf("uses-implied-feature:'android.hardware.screen.landscape'," \
1670 "'one or more activities have specified a landscape orientation'\n");
1671 }
1672 if (reqScreenPortraitFeature) {
1673 printf("uses-feature:'android.hardware.screen.portrait'\n");
1674 printf("uses-implied-feature:'android.hardware.screen.portrait'," \
1675 "'one or more activities have specified a portrait orientation'\n");
1676 }
1677 }
1678
1679 if (hasMainActivity) {
1680 printf("main\n");
1681 }
1682 if (hasWidgetReceivers) {
1683 printf("app-widget\n");
1684 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001685 if (hasDeviceAdminReceiver) {
1686 printf("device-admin\n");
1687 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001688 if (hasImeService) {
1689 printf("ime\n");
1690 }
1691 if (hasWallpaperService) {
1692 printf("wallpaper\n");
1693 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001694 if (hasAccessibilityService) {
1695 printf("accessibility\n");
1696 }
1697 if (hasPrintService) {
1698 printf("print\n");
1699 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001700 if (hasPaymentService) {
1701 printf("payment\n");
1702 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001703 if (hasOtherActivities) {
1704 printf("other-activities\n");
1705 }
1706 if (isSearchable) {
1707 printf("search\n");
1708 }
1709 if (hasOtherReceivers) {
1710 printf("other-receivers\n");
1711 }
1712 if (hasOtherServices) {
1713 printf("other-services\n");
1714 }
1715
1716 // For modern apps, if screen size buckets haven't been specified
1717 // but the new width ranges have, then infer the buckets from them.
1718 if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0
1719 && requiresSmallestWidthDp > 0) {
1720 int compatWidth = compatibleWidthLimitDp;
1721 if (compatWidth <= 0) {
1722 compatWidth = requiresSmallestWidthDp;
1723 }
1724 if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) {
1725 smallScreen = -1;
1726 } else {
1727 smallScreen = 0;
1728 }
1729 if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) {
1730 normalScreen = -1;
1731 } else {
1732 normalScreen = 0;
1733 }
1734 if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) {
1735 largeScreen = -1;
1736 } else {
1737 largeScreen = 0;
1738 }
1739 if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) {
1740 xlargeScreen = -1;
1741 } else {
1742 xlargeScreen = 0;
1743 }
1744 }
1745
1746 // Determine default values for any unspecified screen sizes,
1747 // based on the target SDK of the package. As of 4 (donut)
1748 // the screen size support was introduced, so all default to
1749 // enabled.
1750 if (smallScreen > 0) {
1751 smallScreen = targetSdk >= 4 ? -1 : 0;
1752 }
1753 if (normalScreen > 0) {
1754 normalScreen = -1;
1755 }
1756 if (largeScreen > 0) {
1757 largeScreen = targetSdk >= 4 ? -1 : 0;
1758 }
1759 if (xlargeScreen > 0) {
1760 // Introduced in Gingerbread.
1761 xlargeScreen = targetSdk >= 9 ? -1 : 0;
1762 }
1763 if (anyDensity > 0) {
1764 anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0
1765 || compatibleWidthLimitDp > 0) ? -1 : 0;
1766 }
1767 printf("supports-screens:");
1768 if (smallScreen != 0) {
1769 printf(" 'small'");
1770 }
1771 if (normalScreen != 0) {
1772 printf(" 'normal'");
1773 }
1774 if (largeScreen != 0) {
1775 printf(" 'large'");
1776 }
1777 if (xlargeScreen != 0) {
1778 printf(" 'xlarge'");
1779 }
1780 printf("\n");
1781 printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
1782 if (requiresSmallestWidthDp > 0) {
1783 printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp);
1784 }
1785 if (compatibleWidthLimitDp > 0) {
1786 printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp);
1787 }
1788 if (largestWidthLimitDp > 0) {
1789 printf("largest-width-limit:'%d'\n", largestWidthLimitDp);
1790 }
1791
1792 printf("locales:");
1793 const size_t NL = locales.size();
1794 for (size_t i=0; i<NL; i++) {
1795 const char* localeStr = locales[i].string();
1796 if (localeStr == NULL || strlen(localeStr) == 0) {
1797 localeStr = "--_--";
1798 }
1799 printf(" '%s'", localeStr);
1800 }
1801 printf("\n");
1802
1803 printf("densities:");
1804 const size_t ND = densities.size();
1805 for (size_t i=0; i<ND; i++) {
1806 printf(" '%d'", densities[i]);
1807 }
1808 printf("\n");
1809
1810 AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
1811 if (dir != NULL) {
1812 if (dir->getFileCount() > 0) {
1813 printf("native-code:");
1814 for (size_t i=0; i<dir->getFileCount(); i++) {
1815 printf(" '%s'", dir->getFileName(i).string());
1816 }
1817 printf("\n");
1818 }
1819 delete dir;
1820 }
1821 } else if (strcmp("badger", option) == 0) {
1822 printf("%s", CONSOLE_DATA);
1823 } else if (strcmp("configurations", option) == 0) {
1824 Vector<ResTable_config> configs;
1825 res.getConfigurations(&configs);
1826 const size_t N = configs.size();
1827 for (size_t i=0; i<N; i++) {
1828 printf("%s\n", configs[i].toString().string());
1829 }
1830 } else {
1831 fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
1832 goto bail;
1833 }
1834 }
1835
1836 result = NO_ERROR;
1837
1838bail:
1839 if (asset) {
1840 delete asset;
1841 }
1842 return (result != NO_ERROR);
1843}
1844
1845
1846/*
1847 * Handle the "add" command, which wants to add files to a new or
1848 * pre-existing archive.
1849 */
1850int doAdd(Bundle* bundle)
1851{
1852 ZipFile* zip = NULL;
1853 status_t result = UNKNOWN_ERROR;
1854 const char* zipFileName;
1855
1856 if (bundle->getUpdate()) {
1857 /* avoid confusion */
1858 fprintf(stderr, "ERROR: can't use '-u' with add\n");
1859 goto bail;
1860 }
1861
1862 if (bundle->getFileSpecCount() < 1) {
1863 fprintf(stderr, "ERROR: must specify zip file name\n");
1864 goto bail;
1865 }
1866 zipFileName = bundle->getFileSpecEntry(0);
1867
1868 if (bundle->getFileSpecCount() < 2) {
1869 fprintf(stderr, "NOTE: nothing to do\n");
1870 goto bail;
1871 }
1872
1873 zip = openReadWrite(zipFileName, true);
1874 if (zip == NULL) {
1875 fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
1876 goto bail;
1877 }
1878
1879 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
1880 const char* fileName = bundle->getFileSpecEntry(i);
1881
1882 if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
1883 printf(" '%s'... (from gzip)\n", fileName);
1884 result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
1885 } else {
1886 if (bundle->getJunkPath()) {
1887 String8 storageName = String8(fileName).getPathLeaf();
1888 printf(" '%s' as '%s'...\n", fileName, storageName.string());
1889 result = zip->add(fileName, storageName.string(),
1890 bundle->getCompressionMethod(), NULL);
1891 } else {
1892 printf(" '%s'...\n", fileName);
1893 result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
1894 }
1895 }
1896 if (result != NO_ERROR) {
1897 fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
1898 if (result == NAME_NOT_FOUND) {
1899 fprintf(stderr, ": file not found\n");
1900 } else if (result == ALREADY_EXISTS) {
1901 fprintf(stderr, ": already exists in archive\n");
1902 } else {
1903 fprintf(stderr, "\n");
1904 }
1905 goto bail;
1906 }
1907 }
1908
1909 result = NO_ERROR;
1910
1911bail:
1912 delete zip;
1913 return (result != NO_ERROR);
1914}
1915
1916
1917/*
1918 * Delete files from an existing archive.
1919 */
1920int doRemove(Bundle* bundle)
1921{
1922 ZipFile* zip = NULL;
1923 status_t result = UNKNOWN_ERROR;
1924 const char* zipFileName;
1925
1926 if (bundle->getFileSpecCount() < 1) {
1927 fprintf(stderr, "ERROR: must specify zip file name\n");
1928 goto bail;
1929 }
1930 zipFileName = bundle->getFileSpecEntry(0);
1931
1932 if (bundle->getFileSpecCount() < 2) {
1933 fprintf(stderr, "NOTE: nothing to do\n");
1934 goto bail;
1935 }
1936
1937 zip = openReadWrite(zipFileName, false);
1938 if (zip == NULL) {
1939 fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
1940 zipFileName);
1941 goto bail;
1942 }
1943
1944 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
1945 const char* fileName = bundle->getFileSpecEntry(i);
1946 ZipEntry* entry;
1947
1948 entry = zip->getEntryByName(fileName);
1949 if (entry == NULL) {
1950 printf(" '%s' NOT FOUND\n", fileName);
1951 continue;
1952 }
1953
1954 result = zip->remove(entry);
1955
1956 if (result != NO_ERROR) {
1957 fprintf(stderr, "Unable to delete '%s' from '%s'\n",
1958 bundle->getFileSpecEntry(i), zipFileName);
1959 goto bail;
1960 }
1961 }
1962
1963 /* update the archive */
1964 zip->flush();
1965
1966bail:
1967 delete zip;
1968 return (result != NO_ERROR);
1969}
1970
1971
1972/*
1973 * Package up an asset directory and associated application files.
1974 */
1975int doPackage(Bundle* bundle)
1976{
1977 const char* outputAPKFile;
1978 int retVal = 1;
1979 status_t err;
1980 sp<AaptAssets> assets;
1981 int N;
1982 FILE* fp;
1983 String8 dependencyFile;
1984
1985 // -c zz_ZZ means do pseudolocalization
1986 ResourceFilter filter;
1987 err = filter.parse(bundle->getConfigurations());
1988 if (err != NO_ERROR) {
1989 goto bail;
1990 }
1991 if (filter.containsPseudo()) {
1992 bundle->setPseudolocalize(true);
1993 }
1994
1995 N = bundle->getFileSpecCount();
1996 if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
1997 && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDir() == NULL) {
1998 fprintf(stderr, "ERROR: no input files\n");
1999 goto bail;
2000 }
2001
2002 outputAPKFile = bundle->getOutputAPKFile();
2003
2004 // Make sure the filenames provided exist and are of the appropriate type.
2005 if (outputAPKFile) {
2006 FileType type;
2007 type = getFileType(outputAPKFile);
2008 if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
2009 fprintf(stderr,
2010 "ERROR: output file '%s' exists but is not regular file\n",
2011 outputAPKFile);
2012 goto bail;
2013 }
2014 }
2015
2016 // Load the assets.
2017 assets = new AaptAssets();
2018
2019 // Set up the resource gathering in assets if we're going to generate
2020 // dependency files. Every time we encounter a resource while slurping
2021 // the tree, we'll add it to these stores so we have full resource paths
2022 // to write to a dependency file.
2023 if (bundle->getGenDependencies()) {
2024 sp<FilePathStore> resPathStore = new FilePathStore;
2025 assets->setFullResPaths(resPathStore);
2026 sp<FilePathStore> assetPathStore = new FilePathStore;
2027 assets->setFullAssetPaths(assetPathStore);
2028 }
2029
2030 err = assets->slurpFromArgs(bundle);
2031 if (err < 0) {
2032 goto bail;
2033 }
2034
2035 if (bundle->getVerbose()) {
2036 assets->print(String8());
2037 }
2038
2039 // If they asked for any fileAs that need to be compiled, do so.
2040 if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
2041 err = buildResources(bundle, assets);
2042 if (err != 0) {
2043 goto bail;
2044 }
2045 }
2046
2047 // At this point we've read everything and processed everything. From here
2048 // on out it's just writing output files.
2049 if (SourcePos::hasErrors()) {
2050 goto bail;
2051 }
2052
2053 // Update symbols with information about which ones are needed as Java symbols.
2054 assets->applyJavaSymbols();
2055 if (SourcePos::hasErrors()) {
2056 goto bail;
2057 }
2058
2059 // If we've been asked to generate a dependency file, do that here
2060 if (bundle->getGenDependencies()) {
2061 // If this is the packaging step, generate the dependency file next to
2062 // the output apk (e.g. bin/resources.ap_.d)
2063 if (outputAPKFile) {
2064 dependencyFile = String8(outputAPKFile);
2065 // Add the .d extension to the dependency file.
2066 dependencyFile.append(".d");
2067 } else {
2068 // Else if this is the R.java dependency generation step,
2069 // generate the dependency file in the R.java package subdirectory
2070 // e.g. gen/com/foo/app/R.java.d
2071 dependencyFile = String8(bundle->getRClassDir());
2072 dependencyFile.appendPath("R.java.d");
2073 }
2074 // Make sure we have a clean dependency file to start with
2075 fp = fopen(dependencyFile, "w");
2076 fclose(fp);
2077 }
2078
2079 // Write out R.java constants
2080 if (!assets->havePrivateSymbols()) {
2081 if (bundle->getCustomPackage() == NULL) {
2082 // Write the R.java file into the appropriate class directory
2083 // e.g. gen/com/foo/app/R.java
2084 err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
2085 } else {
2086 const String8 customPkg(bundle->getCustomPackage());
2087 err = writeResourceSymbols(bundle, assets, customPkg, true);
2088 }
2089 if (err < 0) {
2090 goto bail;
2091 }
2092 // If we have library files, we're going to write our R.java file into
2093 // the appropriate class directory for those libraries as well.
2094 // e.g. gen/com/foo/app/lib/R.java
2095 if (bundle->getExtraPackages() != NULL) {
2096 // Split on colon
2097 String8 libs(bundle->getExtraPackages());
2098 char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
2099 while (packageString != NULL) {
2100 // Write the R.java file out with the correct package name
2101 err = writeResourceSymbols(bundle, assets, String8(packageString), true);
2102 if (err < 0) {
2103 goto bail;
2104 }
2105 packageString = strtok(NULL, ":");
2106 }
2107 libs.unlockBuffer();
2108 }
2109 } else {
2110 err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
2111 if (err < 0) {
2112 goto bail;
2113 }
2114 err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
2115 if (err < 0) {
2116 goto bail;
2117 }
2118 }
2119
2120 // Write out the ProGuard file
2121 err = writeProguardFile(bundle, assets);
2122 if (err < 0) {
2123 goto bail;
2124 }
2125
2126 // Write the apk
2127 if (outputAPKFile) {
2128 err = writeAPK(bundle, assets, String8(outputAPKFile));
2129 if (err != NO_ERROR) {
2130 fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile);
2131 goto bail;
2132 }
2133 }
2134
2135 // If we've been asked to generate a dependency file, we need to finish up here.
2136 // the writeResourceSymbols and writeAPK functions have already written the target
2137 // half of the dependency file, now we need to write the prerequisites. (files that
2138 // the R.java file or .ap_ file depend on)
2139 if (bundle->getGenDependencies()) {
2140 // Now that writeResourceSymbols or writeAPK has taken care of writing
2141 // the targets to our dependency file, we'll write the prereqs
2142 fp = fopen(dependencyFile, "a+");
2143 fprintf(fp, " : ");
2144 bool includeRaw = (outputAPKFile != NULL);
2145 err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
2146 // Also manually add the AndroidManifeset since it's not under res/ or assets/
2147 // and therefore was not added to our pathstores during slurping
2148 fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
2149 fclose(fp);
2150 }
2151
2152 retVal = 0;
2153bail:
2154 if (SourcePos::hasErrors()) {
2155 SourcePos::printErrors(stderr);
2156 }
2157 return retVal;
2158}
2159
2160/*
2161 * Do PNG Crunching
2162 * PRECONDITIONS
2163 * -S flag points to a source directory containing drawable* folders
2164 * -C flag points to destination directory. The folder structure in the
2165 * source directory will be mirrored to the destination (cache) directory
2166 *
2167 * POSTCONDITIONS
2168 * Destination directory will be updated to match the PNG files in
2169 * the source directory.
2170 */
2171int doCrunch(Bundle* bundle)
2172{
2173 fprintf(stdout, "Crunching PNG Files in ");
2174 fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
2175 fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
2176
2177 updatePreProcessedCache(bundle);
2178
2179 return NO_ERROR;
2180}
2181
2182/*
2183 * Do PNG Crunching on a single flag
2184 * -i points to a single png file
2185 * -o points to a single png output file
2186 */
2187int doSingleCrunch(Bundle* bundle)
2188{
2189 fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile());
2190 fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile());
2191
2192 String8 input(bundle->getSingleCrunchInputFile());
2193 String8 output(bundle->getSingleCrunchOutputFile());
2194
2195 if (preProcessImageToCache(bundle, input, output) != NO_ERROR) {
2196 // we can't return the status_t as it gets truncate to the lower 8 bits.
2197 return 42;
2198 }
2199
2200 return NO_ERROR;
2201}
2202
2203char CONSOLE_DATA[2925] = {
2204 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2205 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2206 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2207 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2208 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63,
2209 86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83,
2210 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2211 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2212 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81,
2213 81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32,
2214 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2215 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2216 32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59,
2217 59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2218 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2219 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59,
2220 59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32,
2221 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2222 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2223 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87,
2224 58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32,
2225 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2226 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2227 47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58,
2228 121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2229 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2230 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81,
2231 81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32,
2232 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2233 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2234 32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59,
2235 59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2236 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2237 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59,
2238 59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32,
2239 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2240 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2241 32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81,
2242 70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32,
2243 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2244 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2245 32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81,
2246 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2247 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2248 32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109,
2249 81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32,
2250 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2251 32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59,
2252 59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59,
2253 60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2254 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59,
2255 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46,
2256 61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32,
2257 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2258 32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2259 59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81,
2260 109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32,
2261 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81,
2262 67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2263 59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58,
2264 61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32,
2265 32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59,
2266 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37,
2267 73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2268 59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2269 32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59,
2270 59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96,
2271 46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61,
2272 97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32,
2273 46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61,
2274 119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59,
2275 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32,
2276 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2277 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119,
2278 119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2279 59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10,
2280 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2281 32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81,
2282 81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41,
2283 87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2284 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81,
2285 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58,
2286 45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32,
2287 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2288 32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2289 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32,
2290 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2291 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58,
2292 59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58,
2293 59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2294 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2295 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81,
2296 81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32,
2297 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2298 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2299 32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2300 81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2301 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2302 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106,
2303 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59,
2304 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2305 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2306 32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2307 81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32,
2308 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2309 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2310 58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59,
2311 61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2312 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2313 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81,
2314 81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32,
2315 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2316 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2317 32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2318 81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2319 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2320 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59,
2321 59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59,
2322 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2323 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2324 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33,
2325 58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32,
2326 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2327 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2328 61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59,
2329 59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32,
2330 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32,
2331 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95,
2332 32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59,
2333 61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2334 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2335 32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45,
2336 59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32,
2337 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32,
2338 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59,
2339 59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59,
2340 59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2341 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2342 32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2343 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2344 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2345 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2346 46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61,
2347 46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2348 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2349 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59,
2350 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59,
2351 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2352 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2353 32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32,
2354 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32,
2355 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2356 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61,
2357 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59,
2358 59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2359 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2360 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32,
2361 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32,
2362 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2363 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2364 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2365 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2366 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10
2367 };