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