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