blob: 7d188f19d4dc4092c224a3f5d5a0881e8ab0afea [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);
Maurice Chu2675f762013-10-22 17:33:11 -0700392 printf("%s='%s'", attrLabel.string(),
393 ResTable::normalizeForOutput(result.string()).string());
Maurice Chu76327312013-10-16 18:28:46 -0700394 } else if (Res_value::TYPE_FIRST_INT <= value.dataType &&
395 value.dataType <= Res_value::TYPE_LAST_INT) {
396 printf("%s='%d'", attrLabel.string(), value.data);
397 } else {
398 printf("%s='0x%x'", attrLabel.string(), (int)value.data);
399 }
400}
401
Adam Lesinski282e1812014-01-23 18:17:42 -0800402// These are attribute resource constants for the platform, as found
403// in android.R.attr
404enum {
405 LABEL_ATTR = 0x01010001,
406 ICON_ATTR = 0x01010002,
407 NAME_ATTR = 0x01010003,
Adam Lesinskia5018c92013-09-30 16:23:15 -0700408 PERMISSION_ATTR = 0x01010006,
Adam Lesinski94fc9122013-09-30 17:16:09 -0700409 RESOURCE_ATTR = 0x01010025,
Adam Lesinski282e1812014-01-23 18:17:42 -0800410 DEBUGGABLE_ATTR = 0x0101000f,
411 VALUE_ATTR = 0x01010024,
412 VERSION_CODE_ATTR = 0x0101021b,
413 VERSION_NAME_ATTR = 0x0101021c,
414 SCREEN_ORIENTATION_ATTR = 0x0101001e,
415 MIN_SDK_VERSION_ATTR = 0x0101020c,
416 MAX_SDK_VERSION_ATTR = 0x01010271,
417 REQ_TOUCH_SCREEN_ATTR = 0x01010227,
418 REQ_KEYBOARD_TYPE_ATTR = 0x01010228,
419 REQ_HARD_KEYBOARD_ATTR = 0x01010229,
420 REQ_NAVIGATION_ATTR = 0x0101022a,
421 REQ_FIVE_WAY_NAV_ATTR = 0x01010232,
422 TARGET_SDK_VERSION_ATTR = 0x01010270,
423 TEST_ONLY_ATTR = 0x01010272,
424 ANY_DENSITY_ATTR = 0x0101026c,
425 GL_ES_VERSION_ATTR = 0x01010281,
426 SMALL_SCREEN_ATTR = 0x01010284,
427 NORMAL_SCREEN_ATTR = 0x01010285,
428 LARGE_SCREEN_ATTR = 0x01010286,
429 XLARGE_SCREEN_ATTR = 0x010102bf,
430 REQUIRED_ATTR = 0x0101028e,
431 SCREEN_SIZE_ATTR = 0x010102ca,
432 SCREEN_DENSITY_ATTR = 0x010102cb,
433 REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364,
434 COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365,
435 LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366,
436 PUBLIC_KEY_ATTR = 0x010103a6,
Adam Lesinski94fc9122013-09-30 17:16:09 -0700437 CATEGORY_ATTR = 0x010103e8,
Adam Lesinski282e1812014-01-23 18:17:42 -0800438};
439
Maurice Chu2675f762013-10-22 17:33:11 -0700440String8 getComponentName(String8 &pkgName, String8 &componentName) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800441 ssize_t idx = componentName.find(".");
442 String8 retStr(pkgName);
443 if (idx == 0) {
444 retStr += componentName;
445 } else if (idx < 0) {
446 retStr += ".";
447 retStr += componentName;
448 } else {
Maurice Chu2675f762013-10-22 17:33:11 -0700449 return componentName;
Adam Lesinski282e1812014-01-23 18:17:42 -0800450 }
Maurice Chu2675f762013-10-22 17:33:11 -0700451 return retStr;
Adam Lesinski282e1812014-01-23 18:17:42 -0800452}
453
454static void printCompatibleScreens(ResXMLTree& tree) {
455 size_t len;
456 ResXMLTree::event_code_t code;
457 int depth = 0;
458 bool first = true;
459 printf("compatible-screens:");
460 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
461 if (code == ResXMLTree::END_TAG) {
462 depth--;
463 if (depth < 0) {
464 break;
465 }
466 continue;
467 }
468 if (code != ResXMLTree::START_TAG) {
469 continue;
470 }
471 depth++;
472 String8 tag(tree.getElementName(&len));
473 if (tag == "screen") {
474 int32_t screenSize = getIntegerAttribute(tree,
475 SCREEN_SIZE_ATTR, NULL, -1);
476 int32_t screenDensity = getIntegerAttribute(tree,
477 SCREEN_DENSITY_ATTR, NULL, -1);
478 if (screenSize > 0 && screenDensity > 0) {
479 if (!first) {
480 printf(",");
481 }
482 first = false;
483 printf("'%d/%d'", screenSize, screenDensity);
484 }
485 }
486 }
487 printf("\n");
488}
489
Adam Lesinski94fc9122013-09-30 17:16:09 -0700490Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool offHost,
491 String8 *outError = NULL)
492{
493 Asset* aidAsset = assets.openNonAsset(xmlPath, Asset::ACCESS_BUFFER);
494 if (aidAsset == NULL) {
495 if (outError != NULL) *outError = "xml resource does not exist";
496 return Vector<String8>();
497 }
498
499 const String8 serviceTagName(offHost ? "offhost-apdu-service" : "host-apdu-service");
500
501 bool withinApduService = false;
502 Vector<String8> categories;
503
504 String8 error;
505 ResXMLTree tree;
506 tree.setTo(aidAsset->getBuffer(true), aidAsset->getLength());
507
508 size_t len;
509 int depth = 0;
510 ResXMLTree::event_code_t code;
511 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
512 if (code == ResXMLTree::END_TAG) {
513 depth--;
514 String8 tag(tree.getElementName(&len));
515
516 if (depth == 0 && tag == serviceTagName) {
517 withinApduService = false;
518 }
519
520 } else if (code == ResXMLTree::START_TAG) {
521 depth++;
522 String8 tag(tree.getElementName(&len));
523
524 if (depth == 1) {
525 if (tag == serviceTagName) {
526 withinApduService = true;
527 }
528 } else if (depth == 2 && withinApduService) {
529 if (tag == "aid-group") {
530 String8 category = getAttribute(tree, CATEGORY_ATTR, &error);
531 if (error != "") {
532 if (outError != NULL) *outError = error;
533 return Vector<String8>();
534 }
535
536 categories.add(category);
537 }
538 }
539 }
540 }
541 aidAsset->close();
542 return categories;
543}
544
Adam Lesinski282e1812014-01-23 18:17:42 -0800545/*
546 * Handle the "dump" command, to extract select data from an archive.
547 */
548extern char CONSOLE_DATA[2925]; // see EOF
549int doDump(Bundle* bundle)
550{
551 status_t result = UNKNOWN_ERROR;
552 Asset* asset = NULL;
553
554 if (bundle->getFileSpecCount() < 1) {
555 fprintf(stderr, "ERROR: no dump option specified\n");
556 return 1;
557 }
558
559 if (bundle->getFileSpecCount() < 2) {
560 fprintf(stderr, "ERROR: no dump file specified\n");
561 return 1;
562 }
563
564 const char* option = bundle->getFileSpecEntry(0);
565 const char* filename = bundle->getFileSpecEntry(1);
566
567 AssetManager assets;
568 void* assetsCookie;
569 if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
570 fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
571 return 1;
572 }
573
574 // Make a dummy config for retrieving resources... we need to supply
575 // non-default values for some configs so that we can retrieve resources
576 // in the app that don't have a default. The most important of these is
577 // the API version because key resources like icons will have an implicit
578 // version if they are using newer config types like density.
579 ResTable_config config;
580 config.language[0] = 'e';
581 config.language[1] = 'n';
582 config.country[0] = 'U';
583 config.country[1] = 'S';
584 config.orientation = ResTable_config::ORIENTATION_PORT;
585 config.density = ResTable_config::DENSITY_MEDIUM;
586 config.sdkVersion = 10000; // Very high.
587 config.screenWidthDp = 320;
588 config.screenHeightDp = 480;
589 config.smallestScreenWidthDp = 320;
590 assets.setConfiguration(config);
591
592 const ResTable& res = assets.getResources(false);
593 if (&res == NULL) {
594 fprintf(stderr, "ERROR: dump failed because no resource table was found\n");
595 goto bail;
596 }
597
598 if (strcmp("resources", option) == 0) {
599#ifndef HAVE_ANDROID_OS
600 res.print(bundle->getValues());
601#endif
602
603 } else if (strcmp("strings", option) == 0) {
604 const ResStringPool* pool = res.getTableStringBlock(0);
605 printStringPool(pool);
606
607 } else if (strcmp("xmltree", option) == 0) {
608 if (bundle->getFileSpecCount() < 3) {
609 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
610 goto bail;
611 }
612
613 for (int i=2; i<bundle->getFileSpecCount(); i++) {
614 const char* resname = bundle->getFileSpecEntry(i);
615 ResXMLTree tree;
616 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
617 if (asset == NULL) {
618 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
619 goto bail;
620 }
621
622 if (tree.setTo(asset->getBuffer(true),
623 asset->getLength()) != NO_ERROR) {
624 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
625 goto bail;
626 }
627 tree.restart();
628 printXMLBlock(&tree);
629 tree.uninit();
630 delete asset;
631 asset = NULL;
632 }
633
634 } else if (strcmp("xmlstrings", option) == 0) {
635 if (bundle->getFileSpecCount() < 3) {
636 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
637 goto bail;
638 }
639
640 for (int i=2; i<bundle->getFileSpecCount(); i++) {
641 const char* resname = bundle->getFileSpecEntry(i);
642 ResXMLTree tree;
643 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
644 if (asset == NULL) {
645 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
646 goto bail;
647 }
648
649 if (tree.setTo(asset->getBuffer(true),
650 asset->getLength()) != NO_ERROR) {
651 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
652 goto bail;
653 }
654 printStringPool(&tree.getStrings());
655 delete asset;
656 asset = NULL;
657 }
658
659 } else {
660 ResXMLTree tree;
661 asset = assets.openNonAsset("AndroidManifest.xml",
662 Asset::ACCESS_BUFFER);
663 if (asset == NULL) {
664 fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
665 goto bail;
666 }
667
668 if (tree.setTo(asset->getBuffer(true),
669 asset->getLength()) != NO_ERROR) {
670 fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
671 goto bail;
672 }
673 tree.restart();
674
675 if (strcmp("permissions", option) == 0) {
676 size_t len;
677 ResXMLTree::event_code_t code;
678 int depth = 0;
679 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
680 if (code == ResXMLTree::END_TAG) {
681 depth--;
682 continue;
683 }
684 if (code != ResXMLTree::START_TAG) {
685 continue;
686 }
687 depth++;
688 String8 tag(tree.getElementName(&len));
689 //printf("Depth %d tag %s\n", depth, tag.string());
690 if (depth == 1) {
691 if (tag != "manifest") {
692 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
693 goto bail;
694 }
695 String8 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -0700696 printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800697 } else if (depth == 2 && tag == "permission") {
698 String8 error;
699 String8 name = getAttribute(tree, NAME_ATTR, &error);
700 if (error != "") {
701 fprintf(stderr, "ERROR: %s\n", error.string());
702 goto bail;
703 }
Maurice Chu2675f762013-10-22 17:33:11 -0700704 printf("permission: %s\n",
705 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800706 } else if (depth == 2 && tag == "uses-permission") {
707 String8 error;
708 String8 name = getAttribute(tree, NAME_ATTR, &error);
709 if (error != "") {
710 fprintf(stderr, "ERROR: %s\n", error.string());
711 goto bail;
712 }
Maurice Chu2675f762013-10-22 17:33:11 -0700713 printf("uses-permission: %s\n",
714 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800715 int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
716 if (!req) {
Maurice Chu2675f762013-10-22 17:33:11 -0700717 printf("optional-permission: %s\n",
718 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800719 }
720 }
721 }
722 } else if (strcmp("badging", option) == 0) {
723 Vector<String8> locales;
724 res.getLocales(&locales);
725
726 Vector<ResTable_config> configs;
727 res.getConfigurations(&configs);
728 SortedVector<int> densities;
729 const size_t NC = configs.size();
730 for (size_t i=0; i<NC; i++) {
731 int dens = configs[i].density;
732 if (dens == 0) {
733 dens = 160;
734 }
735 densities.add(dens);
736 }
737
738 size_t len;
739 ResXMLTree::event_code_t code;
740 int depth = 0;
741 String8 error;
742 bool withinActivity = false;
743 bool isMainActivity = false;
744 bool isLauncherActivity = false;
745 bool isSearchable = false;
746 bool withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700747 bool withinSupportsInput = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800748 bool withinReceiver = false;
749 bool withinService = false;
750 bool withinIntentFilter = false;
751 bool hasMainActivity = false;
752 bool hasOtherActivities = false;
753 bool hasOtherReceivers = false;
754 bool hasOtherServices = false;
755 bool hasWallpaperService = false;
756 bool hasImeService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700757 bool hasAccessibilityService = false;
758 bool hasPrintService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800759 bool hasWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700760 bool hasDeviceAdminReceiver = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800761 bool hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700762 bool hasPaymentService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800763 bool actMainActivity = false;
764 bool actWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700765 bool actDeviceAdminEnabled = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800766 bool actImeService = false;
767 bool actWallpaperService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700768 bool actAccessibilityService = false;
769 bool actPrintService = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700770 bool actHostApduService = false;
771 bool actOffHostApduService = false;
772 bool hasMetaHostPaymentCategory = false;
773 bool hasMetaOffHostPaymentCategory = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700774
775 // These permissions are required by services implementing services
776 // the system binds to (IME, Accessibility, PrintServices, etc.)
777 bool hasBindDeviceAdminPermission = false;
778 bool hasBindInputMethodPermission = false;
779 bool hasBindAccessibilityServicePermission = false;
780 bool hasBindPrintServicePermission = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700781 bool hasBindNfcServicePermission = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800782
783 // These two implement the implicit permissions that are granted
784 // to pre-1.6 applications.
785 bool hasWriteExternalStoragePermission = false;
786 bool hasReadPhoneStatePermission = false;
787
788 // If an app requests write storage, they will also get read storage.
789 bool hasReadExternalStoragePermission = false;
790
791 // Implement transition to read and write call log.
792 bool hasReadContactsPermission = false;
793 bool hasWriteContactsPermission = false;
794 bool hasReadCallLogPermission = false;
795 bool hasWriteCallLogPermission = false;
796
797 // This next group of variables is used to implement a group of
798 // backward-compatibility heuristics necessitated by the addition of
799 // some new uses-feature constants in 2.1 and 2.2. In most cases, the
800 // heuristic is "if an app requests a permission but doesn't explicitly
801 // request the corresponding <uses-feature>, presume it's there anyway".
802 bool specCameraFeature = false; // camera-related
803 bool specCameraAutofocusFeature = false;
804 bool reqCameraAutofocusFeature = false;
805 bool reqCameraFlashFeature = false;
806 bool hasCameraPermission = false;
807 bool specLocationFeature = false; // location-related
808 bool specNetworkLocFeature = false;
809 bool reqNetworkLocFeature = false;
810 bool specGpsFeature = false;
811 bool reqGpsFeature = false;
812 bool hasMockLocPermission = false;
813 bool hasCoarseLocPermission = false;
814 bool hasGpsPermission = false;
815 bool hasGeneralLocPermission = false;
816 bool specBluetoothFeature = false; // Bluetooth API-related
817 bool hasBluetoothPermission = false;
818 bool specMicrophoneFeature = false; // microphone-related
819 bool hasRecordAudioPermission = false;
820 bool specWiFiFeature = false;
821 bool hasWiFiPermission = false;
822 bool specTelephonyFeature = false; // telephony-related
823 bool reqTelephonySubFeature = false;
824 bool hasTelephonyPermission = false;
825 bool specTouchscreenFeature = false; // touchscreen-related
826 bool specMultitouchFeature = false;
827 bool reqDistinctMultitouchFeature = false;
828 bool specScreenPortraitFeature = false;
829 bool specScreenLandscapeFeature = false;
830 bool reqScreenPortraitFeature = false;
831 bool reqScreenLandscapeFeature = false;
832 // 2.2 also added some other features that apps can request, but that
833 // have no corresponding permission, so we cannot implement any
834 // back-compatibility heuristic for them. The below are thus unnecessary
835 // (but are retained here for documentary purposes.)
836 //bool specCompassFeature = false;
837 //bool specAccelerometerFeature = false;
838 //bool specProximityFeature = false;
839 //bool specAmbientLightFeature = false;
840 //bool specLiveWallpaperFeature = false;
841
842 int targetSdk = 0;
843 int smallScreen = 1;
844 int normalScreen = 1;
845 int largeScreen = 1;
846 int xlargeScreen = 1;
847 int anyDensity = 1;
848 int requiresSmallestWidthDp = 0;
849 int compatibleWidthLimitDp = 0;
850 int largestWidthLimitDp = 0;
851 String8 pkg;
852 String8 activityName;
853 String8 activityLabel;
854 String8 activityIcon;
855 String8 receiverName;
856 String8 serviceName;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700857 Vector<String8> supportedInput;
Adam Lesinski282e1812014-01-23 18:17:42 -0800858 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
859 if (code == ResXMLTree::END_TAG) {
860 depth--;
861 if (depth < 2) {
Michael Wrightec4fdec2013-09-06 16:50:52 -0700862 if (withinSupportsInput && !supportedInput.isEmpty()) {
863 printf("supports-input: '");
864 const size_t N = supportedInput.size();
865 for (size_t i=0; i<N; i++) {
Maurice Chu2675f762013-10-22 17:33:11 -0700866 printf("%s", ResTable::normalizeForOutput(
867 supportedInput[i].string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -0700868 if (i != N - 1) {
869 printf("' '");
870 } else {
871 printf("'\n");
872 }
873 }
874 supportedInput.clear();
875 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800876 withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700877 withinSupportsInput = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800878 } else if (depth < 3) {
879 if (withinActivity && isMainActivity && isLauncherActivity) {
Maurice Chu2675f762013-10-22 17:33:11 -0700880 String8 aName(getComponentName(pkg, activityName));
Adam Lesinski282e1812014-01-23 18:17:42 -0800881 printf("launchable-activity:");
Maurice Chu2675f762013-10-22 17:33:11 -0700882 if (aName.length() > 0) {
883 printf(" name='%s' ",
884 ResTable::normalizeForOutput(aName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800885 }
886 printf(" label='%s' icon='%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -0700887 ResTable::normalizeForOutput(activityLabel.string()).string(),
888 ResTable::normalizeForOutput(activityIcon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800889 }
890 if (!hasIntentFilter) {
891 hasOtherActivities |= withinActivity;
892 hasOtherReceivers |= withinReceiver;
893 hasOtherServices |= withinService;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700894 } else {
895 if (withinService) {
896 hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
897 hasBindNfcServicePermission);
898 hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
899 hasBindNfcServicePermission);
900 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800901 }
902 withinActivity = false;
903 withinService = false;
904 withinReceiver = false;
905 hasIntentFilter = false;
906 isMainActivity = isLauncherActivity = false;
907 } else if (depth < 4) {
908 if (withinIntentFilter) {
909 if (withinActivity) {
910 hasMainActivity |= actMainActivity;
911 hasOtherActivities |= !actMainActivity;
912 } else if (withinReceiver) {
913 hasWidgetReceivers |= actWidgetReceivers;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700914 hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
915 hasBindDeviceAdminPermission);
916 hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
Adam Lesinski282e1812014-01-23 18:17:42 -0800917 } else if (withinService) {
918 hasImeService |= actImeService;
919 hasWallpaperService |= actWallpaperService;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700920 hasAccessibilityService |= (actAccessibilityService &&
921 hasBindAccessibilityServicePermission);
922 hasPrintService |= (actPrintService && hasBindPrintServicePermission);
923 hasOtherServices |= (!actImeService && !actWallpaperService &&
Adam Lesinski94fc9122013-09-30 17:16:09 -0700924 !actAccessibilityService && !actPrintService &&
925 !actHostApduService && !actOffHostApduService);
Adam Lesinski282e1812014-01-23 18:17:42 -0800926 }
927 }
928 withinIntentFilter = false;
929 }
930 continue;
931 }
932 if (code != ResXMLTree::START_TAG) {
933 continue;
934 }
935 depth++;
936 String8 tag(tree.getElementName(&len));
937 //printf("Depth %d, %s\n", depth, tag.string());
938 if (depth == 1) {
939 if (tag != "manifest") {
940 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
941 goto bail;
942 }
943 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -0700944 printf("package: name='%s' ",
945 ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800946 int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
947 if (error != "") {
948 fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
949 goto bail;
950 }
951 if (versionCode > 0) {
952 printf("versionCode='%d' ", versionCode);
953 } else {
954 printf("versionCode='' ");
955 }
956 String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
957 if (error != "") {
958 fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
959 goto bail;
960 }
Maurice Chu2675f762013-10-22 17:33:11 -0700961 printf("versionName='%s'\n",
962 ResTable::normalizeForOutput(versionName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800963 } else if (depth == 2) {
964 withinApplication = false;
965 if (tag == "application") {
966 withinApplication = true;
967
968 String8 label;
969 const size_t NL = locales.size();
970 for (size_t i=0; i<NL; i++) {
971 const char* localeStr = locales[i].string();
972 assets.setLocale(localeStr != NULL ? localeStr : "");
973 String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
974 if (llabel != "") {
975 if (localeStr == NULL || strlen(localeStr) == 0) {
976 label = llabel;
Maurice Chu2675f762013-10-22 17:33:11 -0700977 printf("application-label:'%s'\n",
978 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800979 } else {
980 if (label == "") {
981 label = llabel;
982 }
983 printf("application-label-%s:'%s'\n", localeStr,
Maurice Chu2675f762013-10-22 17:33:11 -0700984 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800985 }
986 }
987 }
988
989 ResTable_config tmpConfig = config;
990 const size_t ND = densities.size();
991 for (size_t i=0; i<ND; i++) {
992 tmpConfig.density = densities[i];
993 assets.setConfiguration(tmpConfig);
994 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
995 if (icon != "") {
Maurice Chu2675f762013-10-22 17:33:11 -0700996 printf("application-icon-%d:'%s'\n", densities[i],
997 ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800998 }
999 }
1000 assets.setConfiguration(config);
1001
1002 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1003 if (error != "") {
1004 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
1005 goto bail;
1006 }
1007 int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
1008 if (error != "") {
1009 fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
1010 goto bail;
1011 }
Maurice Chu2675f762013-10-22 17:33:11 -07001012 printf("application: label='%s' ",
1013 ResTable::normalizeForOutput(label.string()).string());
1014 printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001015 if (testOnly != 0) {
1016 printf("testOnly='%d'\n", testOnly);
1017 }
1018
1019 int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
1020 if (error != "") {
1021 fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
1022 goto bail;
1023 }
1024 if (debuggable != 0) {
1025 printf("application-debuggable\n");
1026 }
1027 } else if (tag == "uses-sdk") {
1028 int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
1029 if (error != "") {
1030 error = "";
1031 String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
1032 if (error != "") {
1033 fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
1034 error.string());
1035 goto bail;
1036 }
1037 if (name == "Donut") targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001038 printf("sdkVersion:'%s'\n",
1039 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001040 } else if (code != -1) {
1041 targetSdk = code;
1042 printf("sdkVersion:'%d'\n", code);
1043 }
1044 code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
1045 if (code != -1) {
1046 printf("maxSdkVersion:'%d'\n", code);
1047 }
1048 code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
1049 if (error != "") {
1050 error = "";
1051 String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
1052 if (error != "") {
1053 fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
1054 error.string());
1055 goto bail;
1056 }
1057 if (name == "Donut" && targetSdk < 4) targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001058 printf("targetSdkVersion:'%s'\n",
1059 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001060 } else if (code != -1) {
1061 if (targetSdk < code) {
1062 targetSdk = code;
1063 }
1064 printf("targetSdkVersion:'%d'\n", code);
1065 }
1066 } else if (tag == "uses-configuration") {
1067 int32_t reqTouchScreen = getIntegerAttribute(tree,
1068 REQ_TOUCH_SCREEN_ATTR, NULL, 0);
1069 int32_t reqKeyboardType = getIntegerAttribute(tree,
1070 REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
1071 int32_t reqHardKeyboard = getIntegerAttribute(tree,
1072 REQ_HARD_KEYBOARD_ATTR, NULL, 0);
1073 int32_t reqNavigation = getIntegerAttribute(tree,
1074 REQ_NAVIGATION_ATTR, NULL, 0);
1075 int32_t reqFiveWayNav = getIntegerAttribute(tree,
1076 REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
1077 printf("uses-configuration:");
1078 if (reqTouchScreen != 0) {
1079 printf(" reqTouchScreen='%d'", reqTouchScreen);
1080 }
1081 if (reqKeyboardType != 0) {
1082 printf(" reqKeyboardType='%d'", reqKeyboardType);
1083 }
1084 if (reqHardKeyboard != 0) {
1085 printf(" reqHardKeyboard='%d'", reqHardKeyboard);
1086 }
1087 if (reqNavigation != 0) {
1088 printf(" reqNavigation='%d'", reqNavigation);
1089 }
1090 if (reqFiveWayNav != 0) {
1091 printf(" reqFiveWayNav='%d'", reqFiveWayNav);
1092 }
1093 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001094 } else if (tag == "supports-input") {
1095 withinSupportsInput = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001096 } else if (tag == "supports-screens") {
1097 smallScreen = getIntegerAttribute(tree,
1098 SMALL_SCREEN_ATTR, NULL, 1);
1099 normalScreen = getIntegerAttribute(tree,
1100 NORMAL_SCREEN_ATTR, NULL, 1);
1101 largeScreen = getIntegerAttribute(tree,
1102 LARGE_SCREEN_ATTR, NULL, 1);
1103 xlargeScreen = getIntegerAttribute(tree,
1104 XLARGE_SCREEN_ATTR, NULL, 1);
1105 anyDensity = getIntegerAttribute(tree,
1106 ANY_DENSITY_ATTR, NULL, 1);
1107 requiresSmallestWidthDp = getIntegerAttribute(tree,
1108 REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
1109 compatibleWidthLimitDp = getIntegerAttribute(tree,
1110 COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1111 largestWidthLimitDp = getIntegerAttribute(tree,
1112 LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1113 } else if (tag == "uses-feature") {
1114 String8 name = getAttribute(tree, NAME_ATTR, &error);
1115
1116 if (name != "" && error == "") {
1117 int req = getIntegerAttribute(tree,
1118 REQUIRED_ATTR, NULL, 1);
1119
1120 if (name == "android.hardware.camera") {
1121 specCameraFeature = true;
1122 } else if (name == "android.hardware.camera.autofocus") {
1123 // these have no corresponding permission to check for,
1124 // but should imply the foundational camera permission
1125 reqCameraAutofocusFeature = reqCameraAutofocusFeature || req;
1126 specCameraAutofocusFeature = true;
1127 } else if (req && (name == "android.hardware.camera.flash")) {
1128 // these have no corresponding permission to check for,
1129 // but should imply the foundational camera permission
1130 reqCameraFlashFeature = true;
1131 } else if (name == "android.hardware.location") {
1132 specLocationFeature = true;
1133 } else if (name == "android.hardware.location.network") {
1134 specNetworkLocFeature = true;
1135 reqNetworkLocFeature = reqNetworkLocFeature || req;
1136 } else if (name == "android.hardware.location.gps") {
1137 specGpsFeature = true;
1138 reqGpsFeature = reqGpsFeature || req;
1139 } else if (name == "android.hardware.bluetooth") {
1140 specBluetoothFeature = true;
1141 } else if (name == "android.hardware.touchscreen") {
1142 specTouchscreenFeature = true;
1143 } else if (name == "android.hardware.touchscreen.multitouch") {
1144 specMultitouchFeature = true;
1145 } else if (name == "android.hardware.touchscreen.multitouch.distinct") {
1146 reqDistinctMultitouchFeature = reqDistinctMultitouchFeature || req;
1147 } else if (name == "android.hardware.microphone") {
1148 specMicrophoneFeature = true;
1149 } else if (name == "android.hardware.wifi") {
1150 specWiFiFeature = true;
1151 } else if (name == "android.hardware.telephony") {
1152 specTelephonyFeature = true;
1153 } else if (req && (name == "android.hardware.telephony.gsm" ||
1154 name == "android.hardware.telephony.cdma")) {
1155 // these have no corresponding permission to check for,
1156 // but should imply the foundational telephony permission
1157 reqTelephonySubFeature = true;
1158 } else if (name == "android.hardware.screen.portrait") {
1159 specScreenPortraitFeature = true;
1160 } else if (name == "android.hardware.screen.landscape") {
1161 specScreenLandscapeFeature = true;
1162 }
1163 printf("uses-feature%s:'%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001164 req ? "" : "-not-required",
1165 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001166 } else {
1167 int vers = getIntegerAttribute(tree,
1168 GL_ES_VERSION_ATTR, &error);
1169 if (error == "") {
1170 printf("uses-gl-es:'0x%x'\n", vers);
1171 }
1172 }
1173 } else if (tag == "uses-permission") {
1174 String8 name = getAttribute(tree, NAME_ATTR, &error);
1175 if (name != "" && error == "") {
1176 if (name == "android.permission.CAMERA") {
1177 hasCameraPermission = true;
1178 } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
1179 hasGpsPermission = true;
1180 } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
1181 hasMockLocPermission = true;
1182 } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
1183 hasCoarseLocPermission = true;
1184 } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
1185 name == "android.permission.INSTALL_LOCATION_PROVIDER") {
1186 hasGeneralLocPermission = true;
1187 } else if (name == "android.permission.BLUETOOTH" ||
1188 name == "android.permission.BLUETOOTH_ADMIN") {
1189 hasBluetoothPermission = true;
1190 } else if (name == "android.permission.RECORD_AUDIO") {
1191 hasRecordAudioPermission = true;
1192 } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
1193 name == "android.permission.CHANGE_WIFI_STATE" ||
1194 name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
1195 hasWiFiPermission = true;
1196 } else if (name == "android.permission.CALL_PHONE" ||
1197 name == "android.permission.CALL_PRIVILEGED" ||
1198 name == "android.permission.MODIFY_PHONE_STATE" ||
1199 name == "android.permission.PROCESS_OUTGOING_CALLS" ||
1200 name == "android.permission.READ_SMS" ||
1201 name == "android.permission.RECEIVE_SMS" ||
1202 name == "android.permission.RECEIVE_MMS" ||
1203 name == "android.permission.RECEIVE_WAP_PUSH" ||
1204 name == "android.permission.SEND_SMS" ||
1205 name == "android.permission.WRITE_APN_SETTINGS" ||
1206 name == "android.permission.WRITE_SMS") {
1207 hasTelephonyPermission = true;
1208 } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
1209 hasWriteExternalStoragePermission = true;
1210 } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
1211 hasReadExternalStoragePermission = true;
1212 } else if (name == "android.permission.READ_PHONE_STATE") {
1213 hasReadPhoneStatePermission = true;
1214 } else if (name == "android.permission.READ_CONTACTS") {
1215 hasReadContactsPermission = true;
1216 } else if (name == "android.permission.WRITE_CONTACTS") {
1217 hasWriteContactsPermission = true;
1218 } else if (name == "android.permission.READ_CALL_LOG") {
1219 hasReadCallLogPermission = true;
1220 } else if (name == "android.permission.WRITE_CALL_LOG") {
1221 hasWriteCallLogPermission = true;
1222 }
Maurice Chu2675f762013-10-22 17:33:11 -07001223 printf("uses-permission:'%s'\n",
1224 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001225 int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
1226 if (!req) {
Maurice Chu2675f762013-10-22 17:33:11 -07001227 printf("optional-permission:'%s'\n",
1228 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001229 }
1230 } else {
1231 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1232 error.string());
1233 goto bail;
1234 }
1235 } else if (tag == "uses-package") {
1236 String8 name = getAttribute(tree, NAME_ATTR, &error);
1237 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001238 printf("uses-package:'%s'\n",
1239 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001240 } else {
1241 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1242 error.string());
1243 goto bail;
1244 }
1245 } else if (tag == "original-package") {
1246 String8 name = getAttribute(tree, NAME_ATTR, &error);
1247 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001248 printf("original-package:'%s'\n",
1249 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001250 } else {
1251 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1252 error.string());
1253 goto bail;
1254 }
1255 } else if (tag == "supports-gl-texture") {
1256 String8 name = getAttribute(tree, NAME_ATTR, &error);
1257 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001258 printf("supports-gl-texture:'%s'\n",
1259 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001260 } else {
1261 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1262 error.string());
1263 goto bail;
1264 }
1265 } else if (tag == "compatible-screens") {
1266 printCompatibleScreens(tree);
1267 depth--;
1268 } else if (tag == "package-verifier") {
1269 String8 name = getAttribute(tree, NAME_ATTR, &error);
1270 if (name != "" && error == "") {
1271 String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
1272 if (publicKey != "" && error == "") {
1273 printf("package-verifier: name='%s' publicKey='%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001274 ResTable::normalizeForOutput(name.string()).string(),
1275 ResTable::normalizeForOutput(publicKey.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001276 }
1277 }
1278 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001279 } else if (depth == 3) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001280 withinActivity = false;
1281 withinReceiver = false;
1282 withinService = false;
1283 hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001284 hasMetaHostPaymentCategory = false;
1285 hasMetaOffHostPaymentCategory = false;
1286 hasBindDeviceAdminPermission = false;
1287 hasBindInputMethodPermission = false;
1288 hasBindAccessibilityServicePermission = false;
1289 hasBindPrintServicePermission = false;
1290 hasBindNfcServicePermission = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001291 if (withinApplication) {
1292 if(tag == "activity") {
1293 withinActivity = true;
1294 activityName = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001295 if (error != "") {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001296 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1297 error.string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001298 goto bail;
1299 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001300
Michael Wrightec4fdec2013-09-06 16:50:52 -07001301 activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1302 if (error != "") {
1303 fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
1304 error.string());
1305 goto bail;
1306 }
1307
1308 activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1309 if (error != "") {
1310 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
1311 error.string());
1312 goto bail;
1313 }
1314
1315 int32_t orien = getResolvedIntegerAttribute(&res, tree,
1316 SCREEN_ORIENTATION_ATTR, &error);
1317 if (error == "") {
1318 if (orien == 0 || orien == 6 || orien == 8) {
1319 // Requests landscape, sensorLandscape, or reverseLandscape.
1320 reqScreenLandscapeFeature = true;
1321 } else if (orien == 1 || orien == 7 || orien == 9) {
1322 // Requests portrait, sensorPortrait, or reversePortrait.
1323 reqScreenPortraitFeature = true;
1324 }
1325 }
1326 } else if (tag == "uses-library") {
1327 String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
1328 if (error != "") {
1329 fprintf(stderr,
1330 "ERROR getting 'android:name' attribute for uses-library"
1331 " %s\n", error.string());
1332 goto bail;
1333 }
1334 int req = getIntegerAttribute(tree,
1335 REQUIRED_ATTR, NULL, 1);
1336 printf("uses-library%s:'%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001337 req ? "" : "-not-required", ResTable::normalizeForOutput(
1338 libraryName.string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001339 } else if (tag == "receiver") {
1340 withinReceiver = true;
1341 receiverName = getAttribute(tree, NAME_ATTR, &error);
1342
1343 if (error != "") {
1344 fprintf(stderr,
1345 "ERROR getting 'android:name' attribute for receiver:"
1346 " %s\n", error.string());
1347 goto bail;
1348 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001349
1350 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1351 if (error == "") {
1352 if (permission == "android.permission.BIND_DEVICE_ADMIN") {
1353 hasBindDeviceAdminPermission = true;
1354 }
1355 } else {
1356 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1357 " receiver '%s': %s\n", receiverName.string(), error.string());
1358 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001359 } else if (tag == "service") {
1360 withinService = true;
1361 serviceName = getAttribute(tree, NAME_ATTR, &error);
1362
1363 if (error != "") {
1364 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1365 "service:%s\n", error.string());
1366 goto bail;
1367 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001368
1369 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1370 if (error == "") {
1371 if (permission == "android.permission.BIND_INPUT_METHOD") {
1372 hasBindInputMethodPermission = true;
1373 } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
1374 hasBindAccessibilityServicePermission = true;
1375 } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
1376 hasBindPrintServicePermission = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001377 } else if (permission == "android.permission.BIND_NFC_SERVICE") {
1378 hasBindNfcServicePermission = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001379 }
1380 } else {
1381 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1382 " service '%s': %s\n", serviceName.string(), error.string());
1383 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001384 } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
1385 String8 metaDataName = getAttribute(tree, NAME_ATTR, &error);
1386 if (error != "") {
1387 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1388 "meta-data:%s\n", error.string());
1389 goto bail;
1390 }
Maurice Chu2675f762013-10-22 17:33:11 -07001391 printf("meta-data: name='%s' ",
1392 ResTable::normalizeForOutput(metaDataName.string()).string());
Maurice Chu76327312013-10-16 18:28:46 -07001393 printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"),
1394 &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001395 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001396 // Try looking for a RESOURCE_ATTR
1397 error = "";
1398 printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR,
1399 String8("resource"), &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001400 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001401 fprintf(stderr, "ERROR getting 'android:value' or "
1402 "'android:resource' attribute for "
1403 "meta-data:%s\n", error.string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001404 goto bail;
1405 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001406 }
Maurice Chu76327312013-10-16 18:28:46 -07001407 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001408 } else if (withinSupportsInput && tag == "input-type") {
1409 String8 name = getAttribute(tree, NAME_ATTR, &error);
1410 if (name != "" && error == "") {
1411 supportedInput.add(name);
1412 } else {
1413 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1414 error.string());
1415 goto bail;
1416 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001417 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001418 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001419 } else if (depth == 4) {
1420 if (tag == "intent-filter") {
1421 hasIntentFilter = true;
1422 withinIntentFilter = true;
1423 actMainActivity = false;
1424 actWidgetReceivers = false;
1425 actImeService = false;
1426 actWallpaperService = false;
1427 actAccessibilityService = false;
1428 actPrintService = false;
1429 actDeviceAdminEnabled = false;
1430 actHostApduService = false;
1431 actOffHostApduService = false;
1432 } else if (withinService && tag == "meta-data") {
1433 String8 name = getAttribute(tree, NAME_ATTR, &error);
1434 if (error != "") {
1435 fprintf(stderr, "ERROR getting 'android:name' attribute for"
1436 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1437 goto bail;
1438 }
1439
1440 if (name == "android.nfc.cardemulation.host_apdu_service" ||
1441 name == "android.nfc.cardemulation.off_host_apdu_service") {
1442 bool offHost = true;
1443 if (name == "android.nfc.cardemulation.host_apdu_service") {
1444 offHost = false;
1445 }
1446
1447 String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
1448 if (error != "") {
1449 fprintf(stderr, "ERROR getting 'android:resource' attribute for"
1450 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1451 goto bail;
1452 }
1453
1454 Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
1455 offHost, &error);
1456 if (error != "") {
1457 fprintf(stderr, "ERROR getting AID category for service '%s'\n",
1458 serviceName.string());
1459 goto bail;
1460 }
1461
1462 const size_t catLen = categories.size();
1463 for (size_t i = 0; i < catLen; i++) {
1464 bool paymentCategory = (categories[i] == "payment");
1465 if (offHost) {
1466 hasMetaOffHostPaymentCategory |= paymentCategory;
1467 } else {
1468 hasMetaHostPaymentCategory |= paymentCategory;
1469 }
1470 }
1471 }
1472 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001473 } else if ((depth == 5) && withinIntentFilter) {
1474 String8 action;
1475 if (tag == "action") {
1476 action = getAttribute(tree, NAME_ATTR, &error);
1477 if (error != "") {
1478 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1479 error.string());
1480 goto bail;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001481 }
1482
Adam Lesinskia5018c92013-09-30 16:23:15 -07001483 if (withinActivity) {
1484 if (action == "android.intent.action.MAIN") {
1485 isMainActivity = true;
1486 actMainActivity = true;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001487 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001488 } else if (withinReceiver) {
1489 if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
1490 actWidgetReceivers = true;
1491 } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
1492 actDeviceAdminEnabled = true;
1493 }
1494 } else if (withinService) {
1495 if (action == "android.view.InputMethod") {
1496 actImeService = true;
1497 } else if (action == "android.service.wallpaper.WallpaperService") {
1498 actWallpaperService = true;
1499 } else if (action == "android.accessibilityservice.AccessibilityService") {
1500 actAccessibilityService = true;
1501 } else if (action == "android.printservice.PrintService") {
1502 actPrintService = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001503 } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
1504 actHostApduService = true;
1505 } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
1506 actOffHostApduService = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001507 }
1508 }
1509 if (action == "android.intent.action.SEARCH") {
1510 isSearchable = true;
1511 }
1512 }
1513
1514 if (tag == "category") {
1515 String8 category = getAttribute(tree, NAME_ATTR, &error);
1516 if (error != "") {
1517 fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
1518 error.string());
1519 goto bail;
1520 }
1521 if (withinActivity) {
1522 if (category == "android.intent.category.LAUNCHER") {
1523 isLauncherActivity = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001524 }
1525 }
1526 }
1527 }
1528 }
1529
1530 // Pre-1.6 implicitly granted permission compatibility logic
1531 if (targetSdk < 4) {
1532 if (!hasWriteExternalStoragePermission) {
1533 printf("uses-permission:'android.permission.WRITE_EXTERNAL_STORAGE'\n");
1534 printf("uses-implied-permission:'android.permission.WRITE_EXTERNAL_STORAGE'," \
1535 "'targetSdkVersion < 4'\n");
1536 hasWriteExternalStoragePermission = true;
1537 }
1538 if (!hasReadPhoneStatePermission) {
1539 printf("uses-permission:'android.permission.READ_PHONE_STATE'\n");
1540 printf("uses-implied-permission:'android.permission.READ_PHONE_STATE'," \
1541 "'targetSdkVersion < 4'\n");
1542 }
1543 }
1544
1545 // If the application has requested WRITE_EXTERNAL_STORAGE, we will
1546 // force them to always take READ_EXTERNAL_STORAGE as well. We always
1547 // do this (regardless of target API version) because we can't have
1548 // an app with write permission but not read permission.
1549 if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
1550 printf("uses-permission:'android.permission.READ_EXTERNAL_STORAGE'\n");
1551 printf("uses-implied-permission:'android.permission.READ_EXTERNAL_STORAGE'," \
1552 "'requested WRITE_EXTERNAL_STORAGE'\n");
1553 }
1554
1555 // Pre-JellyBean call log permission compatibility.
1556 if (targetSdk < 16) {
1557 if (!hasReadCallLogPermission && hasReadContactsPermission) {
1558 printf("uses-permission:'android.permission.READ_CALL_LOG'\n");
1559 printf("uses-implied-permission:'android.permission.READ_CALL_LOG'," \
1560 "'targetSdkVersion < 16 and requested READ_CONTACTS'\n");
1561 }
1562 if (!hasWriteCallLogPermission && hasWriteContactsPermission) {
1563 printf("uses-permission:'android.permission.WRITE_CALL_LOG'\n");
1564 printf("uses-implied-permission:'android.permission.WRITE_CALL_LOG'," \
1565 "'targetSdkVersion < 16 and requested WRITE_CONTACTS'\n");
1566 }
1567 }
1568
1569 /* The following blocks handle printing "inferred" uses-features, based
1570 * on whether related features or permissions are used by the app.
1571 * Note that the various spec*Feature variables denote whether the
1572 * relevant tag was *present* in the AndroidManfest, not that it was
1573 * present and set to true.
1574 */
1575 // Camera-related back-compatibility logic
1576 if (!specCameraFeature) {
1577 if (reqCameraFlashFeature) {
1578 // if app requested a sub-feature (autofocus or flash) and didn't
1579 // request the base camera feature, we infer that it meant to
1580 printf("uses-feature:'android.hardware.camera'\n");
1581 printf("uses-implied-feature:'android.hardware.camera'," \
1582 "'requested android.hardware.camera.flash feature'\n");
1583 } else if (reqCameraAutofocusFeature) {
1584 // if app requested a sub-feature (autofocus or flash) and didn't
1585 // request the base camera feature, we infer that it meant to
1586 printf("uses-feature:'android.hardware.camera'\n");
1587 printf("uses-implied-feature:'android.hardware.camera'," \
1588 "'requested android.hardware.camera.autofocus feature'\n");
1589 } else if (hasCameraPermission) {
Maurice Chu2675f762013-10-22 17:33:11 -07001590 // if app wants to use camera but didn't request the feature, we infer
Adam Lesinski282e1812014-01-23 18:17:42 -08001591 // that it meant to, and further that it wants autofocus
1592 // (which was the 1.0 - 1.5 behavior)
1593 printf("uses-feature:'android.hardware.camera'\n");
1594 if (!specCameraAutofocusFeature) {
1595 printf("uses-feature:'android.hardware.camera.autofocus'\n");
1596 printf("uses-implied-feature:'android.hardware.camera.autofocus'," \
1597 "'requested android.permission.CAMERA permission'\n");
1598 }
1599 }
1600 }
1601
1602 // Location-related back-compatibility logic
1603 if (!specLocationFeature &&
1604 (hasMockLocPermission || hasCoarseLocPermission || hasGpsPermission ||
1605 hasGeneralLocPermission || reqNetworkLocFeature || reqGpsFeature)) {
1606 // if app either takes a location-related permission or requests one of the
1607 // sub-features, we infer that it also meant to request the base location feature
1608 printf("uses-feature:'android.hardware.location'\n");
1609 printf("uses-implied-feature:'android.hardware.location'," \
1610 "'requested a location access permission'\n");
1611 }
1612 if (!specGpsFeature && hasGpsPermission) {
1613 // if app takes GPS (FINE location) perm but does not request the GPS
1614 // feature, we infer that it meant to
1615 printf("uses-feature:'android.hardware.location.gps'\n");
1616 printf("uses-implied-feature:'android.hardware.location.gps'," \
1617 "'requested android.permission.ACCESS_FINE_LOCATION permission'\n");
1618 }
1619 if (!specNetworkLocFeature && hasCoarseLocPermission) {
1620 // if app takes Network location (COARSE location) perm but does not request the
1621 // network location feature, we infer that it meant to
1622 printf("uses-feature:'android.hardware.location.network'\n");
1623 printf("uses-implied-feature:'android.hardware.location.network'," \
1624 "'requested android.permission.ACCESS_COARSE_LOCATION permission'\n");
1625 }
1626
1627 // Bluetooth-related compatibility logic
1628 if (!specBluetoothFeature && hasBluetoothPermission && (targetSdk > 4)) {
1629 // if app takes a Bluetooth permission but does not request the Bluetooth
1630 // feature, we infer that it meant to
1631 printf("uses-feature:'android.hardware.bluetooth'\n");
1632 printf("uses-implied-feature:'android.hardware.bluetooth'," \
1633 "'requested android.permission.BLUETOOTH or android.permission.BLUETOOTH_ADMIN " \
1634 "permission and targetSdkVersion > 4'\n");
1635 }
1636
1637 // Microphone-related compatibility logic
1638 if (!specMicrophoneFeature && hasRecordAudioPermission) {
1639 // if app takes the record-audio permission but does not request the microphone
1640 // feature, we infer that it meant to
1641 printf("uses-feature:'android.hardware.microphone'\n");
1642 printf("uses-implied-feature:'android.hardware.microphone'," \
1643 "'requested android.permission.RECORD_AUDIO permission'\n");
1644 }
1645
1646 // WiFi-related compatibility logic
1647 if (!specWiFiFeature && hasWiFiPermission) {
1648 // if app takes one of the WiFi permissions but does not request the WiFi
1649 // feature, we infer that it meant to
1650 printf("uses-feature:'android.hardware.wifi'\n");
1651 printf("uses-implied-feature:'android.hardware.wifi'," \
1652 "'requested android.permission.ACCESS_WIFI_STATE, " \
1653 "android.permission.CHANGE_WIFI_STATE, or " \
1654 "android.permission.CHANGE_WIFI_MULTICAST_STATE permission'\n");
1655 }
1656
1657 // Telephony-related compatibility logic
1658 if (!specTelephonyFeature && (hasTelephonyPermission || reqTelephonySubFeature)) {
1659 // if app takes one of the telephony permissions or requests a sub-feature but
1660 // does not request the base telephony feature, we infer that it meant to
1661 printf("uses-feature:'android.hardware.telephony'\n");
1662 printf("uses-implied-feature:'android.hardware.telephony'," \
1663 "'requested a telephony-related permission or feature'\n");
1664 }
1665
1666 // Touchscreen-related back-compatibility logic
1667 if (!specTouchscreenFeature) { // not a typo!
1668 // all apps are presumed to require a touchscreen, unless they explicitly say
1669 // <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
1670 // Note that specTouchscreenFeature is true if the tag is present, regardless
1671 // of whether its value is true or false, so this is safe
1672 printf("uses-feature:'android.hardware.touchscreen'\n");
1673 printf("uses-implied-feature:'android.hardware.touchscreen'," \
1674 "'assumed you require a touch screen unless explicitly made optional'\n");
1675 }
1676 if (!specMultitouchFeature && reqDistinctMultitouchFeature) {
1677 // if app takes one of the telephony permissions or requests a sub-feature but
1678 // does not request the base telephony feature, we infer that it meant to
1679 printf("uses-feature:'android.hardware.touchscreen.multitouch'\n");
1680 printf("uses-implied-feature:'android.hardware.touchscreen.multitouch'," \
1681 "'requested android.hardware.touchscreen.multitouch.distinct feature'\n");
1682 }
1683
1684 // Landscape/portrait-related compatibility logic
1685 if (!specScreenLandscapeFeature && !specScreenPortraitFeature) {
1686 // If the app has specified any activities in its manifest
1687 // that request a specific orientation, then assume that
1688 // orientation is required.
1689 if (reqScreenLandscapeFeature) {
1690 printf("uses-feature:'android.hardware.screen.landscape'\n");
1691 printf("uses-implied-feature:'android.hardware.screen.landscape'," \
1692 "'one or more activities have specified a landscape orientation'\n");
1693 }
1694 if (reqScreenPortraitFeature) {
1695 printf("uses-feature:'android.hardware.screen.portrait'\n");
1696 printf("uses-implied-feature:'android.hardware.screen.portrait'," \
1697 "'one or more activities have specified a portrait orientation'\n");
1698 }
1699 }
1700
1701 if (hasMainActivity) {
1702 printf("main\n");
1703 }
1704 if (hasWidgetReceivers) {
1705 printf("app-widget\n");
1706 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001707 if (hasDeviceAdminReceiver) {
1708 printf("device-admin\n");
1709 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001710 if (hasImeService) {
1711 printf("ime\n");
1712 }
1713 if (hasWallpaperService) {
1714 printf("wallpaper\n");
1715 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001716 if (hasAccessibilityService) {
1717 printf("accessibility\n");
1718 }
1719 if (hasPrintService) {
1720 printf("print\n");
1721 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001722 if (hasPaymentService) {
1723 printf("payment\n");
1724 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001725 if (hasOtherActivities) {
1726 printf("other-activities\n");
1727 }
1728 if (isSearchable) {
1729 printf("search\n");
1730 }
1731 if (hasOtherReceivers) {
1732 printf("other-receivers\n");
1733 }
1734 if (hasOtherServices) {
1735 printf("other-services\n");
1736 }
1737
1738 // For modern apps, if screen size buckets haven't been specified
1739 // but the new width ranges have, then infer the buckets from them.
1740 if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0
1741 && requiresSmallestWidthDp > 0) {
1742 int compatWidth = compatibleWidthLimitDp;
1743 if (compatWidth <= 0) {
1744 compatWidth = requiresSmallestWidthDp;
1745 }
1746 if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) {
1747 smallScreen = -1;
1748 } else {
1749 smallScreen = 0;
1750 }
1751 if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) {
1752 normalScreen = -1;
1753 } else {
1754 normalScreen = 0;
1755 }
1756 if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) {
1757 largeScreen = -1;
1758 } else {
1759 largeScreen = 0;
1760 }
1761 if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) {
1762 xlargeScreen = -1;
1763 } else {
1764 xlargeScreen = 0;
1765 }
1766 }
1767
1768 // Determine default values for any unspecified screen sizes,
1769 // based on the target SDK of the package. As of 4 (donut)
1770 // the screen size support was introduced, so all default to
1771 // enabled.
1772 if (smallScreen > 0) {
1773 smallScreen = targetSdk >= 4 ? -1 : 0;
1774 }
1775 if (normalScreen > 0) {
1776 normalScreen = -1;
1777 }
1778 if (largeScreen > 0) {
1779 largeScreen = targetSdk >= 4 ? -1 : 0;
1780 }
1781 if (xlargeScreen > 0) {
1782 // Introduced in Gingerbread.
1783 xlargeScreen = targetSdk >= 9 ? -1 : 0;
1784 }
1785 if (anyDensity > 0) {
1786 anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0
1787 || compatibleWidthLimitDp > 0) ? -1 : 0;
1788 }
1789 printf("supports-screens:");
1790 if (smallScreen != 0) {
1791 printf(" 'small'");
1792 }
1793 if (normalScreen != 0) {
1794 printf(" 'normal'");
1795 }
1796 if (largeScreen != 0) {
1797 printf(" 'large'");
1798 }
1799 if (xlargeScreen != 0) {
1800 printf(" 'xlarge'");
1801 }
1802 printf("\n");
1803 printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
1804 if (requiresSmallestWidthDp > 0) {
1805 printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp);
1806 }
1807 if (compatibleWidthLimitDp > 0) {
1808 printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp);
1809 }
1810 if (largestWidthLimitDp > 0) {
1811 printf("largest-width-limit:'%d'\n", largestWidthLimitDp);
1812 }
1813
1814 printf("locales:");
1815 const size_t NL = locales.size();
1816 for (size_t i=0; i<NL; i++) {
1817 const char* localeStr = locales[i].string();
1818 if (localeStr == NULL || strlen(localeStr) == 0) {
1819 localeStr = "--_--";
1820 }
1821 printf(" '%s'", localeStr);
1822 }
1823 printf("\n");
1824
1825 printf("densities:");
1826 const size_t ND = densities.size();
1827 for (size_t i=0; i<ND; i++) {
1828 printf(" '%d'", densities[i]);
1829 }
1830 printf("\n");
1831
1832 AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
1833 if (dir != NULL) {
1834 if (dir->getFileCount() > 0) {
1835 printf("native-code:");
1836 for (size_t i=0; i<dir->getFileCount(); i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07001837 printf(" '%s'", ResTable::normalizeForOutput(
1838 dir->getFileName(i).string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001839 }
1840 printf("\n");
1841 }
1842 delete dir;
1843 }
1844 } else if (strcmp("badger", option) == 0) {
1845 printf("%s", CONSOLE_DATA);
1846 } else if (strcmp("configurations", option) == 0) {
1847 Vector<ResTable_config> configs;
1848 res.getConfigurations(&configs);
1849 const size_t N = configs.size();
1850 for (size_t i=0; i<N; i++) {
1851 printf("%s\n", configs[i].toString().string());
1852 }
1853 } else {
1854 fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
1855 goto bail;
1856 }
1857 }
1858
1859 result = NO_ERROR;
1860
1861bail:
1862 if (asset) {
1863 delete asset;
1864 }
1865 return (result != NO_ERROR);
1866}
1867
1868
1869/*
1870 * Handle the "add" command, which wants to add files to a new or
1871 * pre-existing archive.
1872 */
1873int doAdd(Bundle* bundle)
1874{
1875 ZipFile* zip = NULL;
1876 status_t result = UNKNOWN_ERROR;
1877 const char* zipFileName;
1878
1879 if (bundle->getUpdate()) {
1880 /* avoid confusion */
1881 fprintf(stderr, "ERROR: can't use '-u' with add\n");
1882 goto bail;
1883 }
1884
1885 if (bundle->getFileSpecCount() < 1) {
1886 fprintf(stderr, "ERROR: must specify zip file name\n");
1887 goto bail;
1888 }
1889 zipFileName = bundle->getFileSpecEntry(0);
1890
1891 if (bundle->getFileSpecCount() < 2) {
1892 fprintf(stderr, "NOTE: nothing to do\n");
1893 goto bail;
1894 }
1895
1896 zip = openReadWrite(zipFileName, true);
1897 if (zip == NULL) {
1898 fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
1899 goto bail;
1900 }
1901
1902 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
1903 const char* fileName = bundle->getFileSpecEntry(i);
1904
1905 if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
1906 printf(" '%s'... (from gzip)\n", fileName);
1907 result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
1908 } else {
1909 if (bundle->getJunkPath()) {
1910 String8 storageName = String8(fileName).getPathLeaf();
Maurice Chu2675f762013-10-22 17:33:11 -07001911 printf(" '%s' as '%s'...\n", fileName,
1912 ResTable::normalizeForOutput(storageName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001913 result = zip->add(fileName, storageName.string(),
1914 bundle->getCompressionMethod(), NULL);
1915 } else {
1916 printf(" '%s'...\n", fileName);
1917 result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
1918 }
1919 }
1920 if (result != NO_ERROR) {
1921 fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
1922 if (result == NAME_NOT_FOUND) {
1923 fprintf(stderr, ": file not found\n");
1924 } else if (result == ALREADY_EXISTS) {
1925 fprintf(stderr, ": already exists in archive\n");
1926 } else {
1927 fprintf(stderr, "\n");
1928 }
1929 goto bail;
1930 }
1931 }
1932
1933 result = NO_ERROR;
1934
1935bail:
1936 delete zip;
1937 return (result != NO_ERROR);
1938}
1939
1940
1941/*
1942 * Delete files from an existing archive.
1943 */
1944int doRemove(Bundle* bundle)
1945{
1946 ZipFile* zip = NULL;
1947 status_t result = UNKNOWN_ERROR;
1948 const char* zipFileName;
1949
1950 if (bundle->getFileSpecCount() < 1) {
1951 fprintf(stderr, "ERROR: must specify zip file name\n");
1952 goto bail;
1953 }
1954 zipFileName = bundle->getFileSpecEntry(0);
1955
1956 if (bundle->getFileSpecCount() < 2) {
1957 fprintf(stderr, "NOTE: nothing to do\n");
1958 goto bail;
1959 }
1960
1961 zip = openReadWrite(zipFileName, false);
1962 if (zip == NULL) {
1963 fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
1964 zipFileName);
1965 goto bail;
1966 }
1967
1968 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
1969 const char* fileName = bundle->getFileSpecEntry(i);
1970 ZipEntry* entry;
1971
1972 entry = zip->getEntryByName(fileName);
1973 if (entry == NULL) {
1974 printf(" '%s' NOT FOUND\n", fileName);
1975 continue;
1976 }
1977
1978 result = zip->remove(entry);
1979
1980 if (result != NO_ERROR) {
1981 fprintf(stderr, "Unable to delete '%s' from '%s'\n",
1982 bundle->getFileSpecEntry(i), zipFileName);
1983 goto bail;
1984 }
1985 }
1986
1987 /* update the archive */
1988 zip->flush();
1989
1990bail:
1991 delete zip;
1992 return (result != NO_ERROR);
1993}
1994
1995
1996/*
1997 * Package up an asset directory and associated application files.
1998 */
1999int doPackage(Bundle* bundle)
2000{
2001 const char* outputAPKFile;
2002 int retVal = 1;
2003 status_t err;
2004 sp<AaptAssets> assets;
2005 int N;
2006 FILE* fp;
2007 String8 dependencyFile;
2008
2009 // -c zz_ZZ means do pseudolocalization
2010 ResourceFilter filter;
2011 err = filter.parse(bundle->getConfigurations());
2012 if (err != NO_ERROR) {
2013 goto bail;
2014 }
2015 if (filter.containsPseudo()) {
2016 bundle->setPseudolocalize(true);
2017 }
2018
2019 N = bundle->getFileSpecCount();
2020 if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
2021 && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDir() == NULL) {
2022 fprintf(stderr, "ERROR: no input files\n");
2023 goto bail;
2024 }
2025
2026 outputAPKFile = bundle->getOutputAPKFile();
2027
2028 // Make sure the filenames provided exist and are of the appropriate type.
2029 if (outputAPKFile) {
2030 FileType type;
2031 type = getFileType(outputAPKFile);
2032 if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
2033 fprintf(stderr,
2034 "ERROR: output file '%s' exists but is not regular file\n",
2035 outputAPKFile);
2036 goto bail;
2037 }
2038 }
2039
2040 // Load the assets.
2041 assets = new AaptAssets();
2042
2043 // Set up the resource gathering in assets if we're going to generate
2044 // dependency files. Every time we encounter a resource while slurping
2045 // the tree, we'll add it to these stores so we have full resource paths
2046 // to write to a dependency file.
2047 if (bundle->getGenDependencies()) {
2048 sp<FilePathStore> resPathStore = new FilePathStore;
2049 assets->setFullResPaths(resPathStore);
2050 sp<FilePathStore> assetPathStore = new FilePathStore;
2051 assets->setFullAssetPaths(assetPathStore);
2052 }
2053
2054 err = assets->slurpFromArgs(bundle);
2055 if (err < 0) {
2056 goto bail;
2057 }
2058
2059 if (bundle->getVerbose()) {
2060 assets->print(String8());
2061 }
2062
2063 // If they asked for any fileAs that need to be compiled, do so.
2064 if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
2065 err = buildResources(bundle, assets);
2066 if (err != 0) {
2067 goto bail;
2068 }
2069 }
2070
2071 // At this point we've read everything and processed everything. From here
2072 // on out it's just writing output files.
2073 if (SourcePos::hasErrors()) {
2074 goto bail;
2075 }
2076
2077 // Update symbols with information about which ones are needed as Java symbols.
2078 assets->applyJavaSymbols();
2079 if (SourcePos::hasErrors()) {
2080 goto bail;
2081 }
2082
2083 // If we've been asked to generate a dependency file, do that here
2084 if (bundle->getGenDependencies()) {
2085 // If this is the packaging step, generate the dependency file next to
2086 // the output apk (e.g. bin/resources.ap_.d)
2087 if (outputAPKFile) {
2088 dependencyFile = String8(outputAPKFile);
2089 // Add the .d extension to the dependency file.
2090 dependencyFile.append(".d");
2091 } else {
2092 // Else if this is the R.java dependency generation step,
2093 // generate the dependency file in the R.java package subdirectory
2094 // e.g. gen/com/foo/app/R.java.d
2095 dependencyFile = String8(bundle->getRClassDir());
2096 dependencyFile.appendPath("R.java.d");
2097 }
2098 // Make sure we have a clean dependency file to start with
2099 fp = fopen(dependencyFile, "w");
2100 fclose(fp);
2101 }
2102
2103 // Write out R.java constants
2104 if (!assets->havePrivateSymbols()) {
2105 if (bundle->getCustomPackage() == NULL) {
2106 // Write the R.java file into the appropriate class directory
2107 // e.g. gen/com/foo/app/R.java
2108 err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
2109 } else {
2110 const String8 customPkg(bundle->getCustomPackage());
2111 err = writeResourceSymbols(bundle, assets, customPkg, true);
2112 }
2113 if (err < 0) {
2114 goto bail;
2115 }
2116 // If we have library files, we're going to write our R.java file into
2117 // the appropriate class directory for those libraries as well.
2118 // e.g. gen/com/foo/app/lib/R.java
2119 if (bundle->getExtraPackages() != NULL) {
2120 // Split on colon
2121 String8 libs(bundle->getExtraPackages());
2122 char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
2123 while (packageString != NULL) {
2124 // Write the R.java file out with the correct package name
2125 err = writeResourceSymbols(bundle, assets, String8(packageString), true);
2126 if (err < 0) {
2127 goto bail;
2128 }
2129 packageString = strtok(NULL, ":");
2130 }
2131 libs.unlockBuffer();
2132 }
2133 } else {
2134 err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
2135 if (err < 0) {
2136 goto bail;
2137 }
2138 err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
2139 if (err < 0) {
2140 goto bail;
2141 }
2142 }
2143
2144 // Write out the ProGuard file
2145 err = writeProguardFile(bundle, assets);
2146 if (err < 0) {
2147 goto bail;
2148 }
2149
2150 // Write the apk
2151 if (outputAPKFile) {
2152 err = writeAPK(bundle, assets, String8(outputAPKFile));
2153 if (err != NO_ERROR) {
2154 fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile);
2155 goto bail;
2156 }
2157 }
2158
2159 // If we've been asked to generate a dependency file, we need to finish up here.
2160 // the writeResourceSymbols and writeAPK functions have already written the target
2161 // half of the dependency file, now we need to write the prerequisites. (files that
2162 // the R.java file or .ap_ file depend on)
2163 if (bundle->getGenDependencies()) {
2164 // Now that writeResourceSymbols or writeAPK has taken care of writing
2165 // the targets to our dependency file, we'll write the prereqs
2166 fp = fopen(dependencyFile, "a+");
2167 fprintf(fp, " : ");
2168 bool includeRaw = (outputAPKFile != NULL);
2169 err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
2170 // Also manually add the AndroidManifeset since it's not under res/ or assets/
2171 // and therefore was not added to our pathstores during slurping
2172 fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
2173 fclose(fp);
2174 }
2175
2176 retVal = 0;
2177bail:
2178 if (SourcePos::hasErrors()) {
2179 SourcePos::printErrors(stderr);
2180 }
2181 return retVal;
2182}
2183
2184/*
2185 * Do PNG Crunching
2186 * PRECONDITIONS
2187 * -S flag points to a source directory containing drawable* folders
2188 * -C flag points to destination directory. The folder structure in the
2189 * source directory will be mirrored to the destination (cache) directory
2190 *
2191 * POSTCONDITIONS
2192 * Destination directory will be updated to match the PNG files in
Maurice Chu2675f762013-10-22 17:33:11 -07002193 * the source directory.
Adam Lesinski282e1812014-01-23 18:17:42 -08002194 */
2195int doCrunch(Bundle* bundle)
2196{
2197 fprintf(stdout, "Crunching PNG Files in ");
2198 fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
2199 fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
2200
2201 updatePreProcessedCache(bundle);
2202
2203 return NO_ERROR;
2204}
2205
2206/*
2207 * Do PNG Crunching on a single flag
2208 * -i points to a single png file
2209 * -o points to a single png output file
2210 */
2211int doSingleCrunch(Bundle* bundle)
2212{
2213 fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile());
2214 fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile());
2215
2216 String8 input(bundle->getSingleCrunchInputFile());
2217 String8 output(bundle->getSingleCrunchOutputFile());
2218
2219 if (preProcessImageToCache(bundle, input, output) != NO_ERROR) {
2220 // we can't return the status_t as it gets truncate to the lower 8 bits.
2221 return 42;
2222 }
2223
2224 return NO_ERROR;
2225}
2226
2227char CONSOLE_DATA[2925] = {
2228 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2229 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2230 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2231 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2232 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63,
2233 86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83,
2234 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2235 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2236 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81,
2237 81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32,
2238 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2239 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2240 32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59,
2241 59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2242 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2243 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59,
2244 59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32,
2245 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2246 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2247 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87,
2248 58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32,
2249 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2250 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2251 47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58,
2252 121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2253 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2254 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81,
2255 81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32,
2256 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2257 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2258 32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59,
2259 59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2260 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2261 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59,
2262 59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32,
2263 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2264 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2265 32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81,
2266 70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32,
2267 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2268 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2269 32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81,
2270 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2271 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2272 32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109,
2273 81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32,
2274 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2275 32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59,
2276 59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59,
2277 60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2278 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59,
2279 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46,
2280 61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32,
2281 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2282 32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2283 59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81,
2284 109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32,
2285 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81,
2286 67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2287 59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58,
2288 61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32,
2289 32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59,
2290 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37,
2291 73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2292 59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2293 32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59,
2294 59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96,
2295 46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61,
2296 97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32,
2297 46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61,
2298 119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59,
2299 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32,
2300 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2301 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119,
2302 119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2303 59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10,
2304 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2305 32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81,
2306 81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41,
2307 87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2308 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81,
2309 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58,
2310 45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32,
2311 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2312 32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2313 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32,
2314 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2315 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58,
2316 59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58,
2317 59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2318 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2319 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81,
2320 81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32,
2321 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2322 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2323 32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2324 81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2325 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2326 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106,
2327 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59,
2328 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2329 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2330 32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2331 81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32,
2332 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2333 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2334 58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59,
2335 61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2336 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2337 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81,
2338 81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32,
2339 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2340 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2341 32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2342 81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2343 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2344 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59,
2345 59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59,
2346 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2347 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2348 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33,
2349 58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32,
2350 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2351 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2352 61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59,
2353 59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32,
2354 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32,
2355 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95,
2356 32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59,
2357 61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2358 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2359 32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45,
2360 59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32,
2361 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32,
2362 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59,
2363 59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59,
2364 59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2365 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2366 32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2367 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2368 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2369 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2370 46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61,
2371 46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2372 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2373 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59,
2374 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59,
2375 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2376 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2377 32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32,
2378 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32,
2379 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2380 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61,
2381 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59,
2382 59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2383 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2384 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32,
2385 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32,
2386 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2387 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2388 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2389 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2390 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10
2391 };