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