blob: 5fefab66b331213cdb5296ac260ec31a7313fe2c [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 Lesinski9d5b08e2014-04-25 11:01:43 -0700410 EXPORTED_ATTR = 0x01010010,
411 GRANT_URI_PERMISSIONS_ATTR = 0x0101001b,
Adam Lesinski94fc9122013-09-30 17:16:09 -0700412 RESOURCE_ATTR = 0x01010025,
Adam Lesinski282e1812014-01-23 18:17:42 -0800413 DEBUGGABLE_ATTR = 0x0101000f,
414 VALUE_ATTR = 0x01010024,
415 VERSION_CODE_ATTR = 0x0101021b,
416 VERSION_NAME_ATTR = 0x0101021c,
417 SCREEN_ORIENTATION_ATTR = 0x0101001e,
418 MIN_SDK_VERSION_ATTR = 0x0101020c,
419 MAX_SDK_VERSION_ATTR = 0x01010271,
420 REQ_TOUCH_SCREEN_ATTR = 0x01010227,
421 REQ_KEYBOARD_TYPE_ATTR = 0x01010228,
422 REQ_HARD_KEYBOARD_ATTR = 0x01010229,
423 REQ_NAVIGATION_ATTR = 0x0101022a,
424 REQ_FIVE_WAY_NAV_ATTR = 0x01010232,
425 TARGET_SDK_VERSION_ATTR = 0x01010270,
426 TEST_ONLY_ATTR = 0x01010272,
427 ANY_DENSITY_ATTR = 0x0101026c,
428 GL_ES_VERSION_ATTR = 0x01010281,
429 SMALL_SCREEN_ATTR = 0x01010284,
430 NORMAL_SCREEN_ATTR = 0x01010285,
431 LARGE_SCREEN_ATTR = 0x01010286,
432 XLARGE_SCREEN_ATTR = 0x010102bf,
433 REQUIRED_ATTR = 0x0101028e,
434 SCREEN_SIZE_ATTR = 0x010102ca,
435 SCREEN_DENSITY_ATTR = 0x010102cb,
436 REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364,
437 COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365,
438 LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366,
439 PUBLIC_KEY_ATTR = 0x010103a6,
Adam Lesinski94fc9122013-09-30 17:16:09 -0700440 CATEGORY_ATTR = 0x010103e8,
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800441 BANNER_ATTR = 0x10103f2,
Adam Lesinski282e1812014-01-23 18:17:42 -0800442};
443
Maurice Chu2675f762013-10-22 17:33:11 -0700444String8 getComponentName(String8 &pkgName, String8 &componentName) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800445 ssize_t idx = componentName.find(".");
446 String8 retStr(pkgName);
447 if (idx == 0) {
448 retStr += componentName;
449 } else if (idx < 0) {
450 retStr += ".";
451 retStr += componentName;
452 } else {
Maurice Chu2675f762013-10-22 17:33:11 -0700453 return componentName;
Adam Lesinski282e1812014-01-23 18:17:42 -0800454 }
Maurice Chu2675f762013-10-22 17:33:11 -0700455 return retStr;
Adam Lesinski282e1812014-01-23 18:17:42 -0800456}
457
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700458static void printCompatibleScreens(ResXMLTree& tree, String8* outError) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800459 size_t len;
460 ResXMLTree::event_code_t code;
461 int depth = 0;
462 bool first = true;
463 printf("compatible-screens:");
464 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
465 if (code == ResXMLTree::END_TAG) {
466 depth--;
467 if (depth < 0) {
468 break;
469 }
470 continue;
471 }
472 if (code != ResXMLTree::START_TAG) {
473 continue;
474 }
475 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700476 const char16_t* ctag16 = tree.getElementName(&len);
477 if (ctag16 == NULL) {
478 *outError = "failed to get XML element name (bad string pool)";
479 return;
480 }
481 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -0800482 if (tag == "screen") {
483 int32_t screenSize = getIntegerAttribute(tree,
484 SCREEN_SIZE_ATTR, NULL, -1);
485 int32_t screenDensity = getIntegerAttribute(tree,
486 SCREEN_DENSITY_ATTR, NULL, -1);
487 if (screenSize > 0 && screenDensity > 0) {
488 if (!first) {
489 printf(",");
490 }
491 first = false;
492 printf("'%d/%d'", screenSize, screenDensity);
493 }
494 }
495 }
496 printf("\n");
497}
498
Adam Lesinski58f1f362013-11-12 12:59:08 -0800499static void printUsesPermission(const String8& name, bool optional=false, int maxSdkVersion=-1) {
500 printf("uses-permission: name='%s'", ResTable::normalizeForOutput(name.string()).string());
501 if (maxSdkVersion != -1) {
502 printf(" maxSdkVersion='%d'", maxSdkVersion);
503 }
504 printf("\n");
505
506 if (optional) {
507 printf("optional-permission: name='%s'",
508 ResTable::normalizeForOutput(name.string()).string());
509 if (maxSdkVersion != -1) {
510 printf(" maxSdkVersion='%d'", maxSdkVersion);
511 }
512 printf("\n");
513 }
514}
515
516static void printUsesImpliedPermission(const String8& name, const String8& reason) {
517 printf("uses-implied-permission: name='%s' reason='%s'\n",
518 ResTable::normalizeForOutput(name.string()).string(),
519 ResTable::normalizeForOutput(reason.string()).string());
520}
521
Adam Lesinski94fc9122013-09-30 17:16:09 -0700522Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool offHost,
523 String8 *outError = NULL)
524{
525 Asset* aidAsset = assets.openNonAsset(xmlPath, Asset::ACCESS_BUFFER);
526 if (aidAsset == NULL) {
527 if (outError != NULL) *outError = "xml resource does not exist";
528 return Vector<String8>();
529 }
530
531 const String8 serviceTagName(offHost ? "offhost-apdu-service" : "host-apdu-service");
532
533 bool withinApduService = false;
534 Vector<String8> categories;
535
536 String8 error;
537 ResXMLTree tree;
538 tree.setTo(aidAsset->getBuffer(true), aidAsset->getLength());
539
540 size_t len;
541 int depth = 0;
542 ResXMLTree::event_code_t code;
543 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
544 if (code == ResXMLTree::END_TAG) {
545 depth--;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700546 const char16_t* ctag16 = tree.getElementName(&len);
547 if (ctag16 == NULL) {
548 *outError = "failed to get XML element name (bad string pool)";
549 return Vector<String8>();
550 }
551 String8 tag(ctag16);
Adam Lesinski94fc9122013-09-30 17:16:09 -0700552
553 if (depth == 0 && tag == serviceTagName) {
554 withinApduService = false;
555 }
556
557 } else if (code == ResXMLTree::START_TAG) {
558 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700559 const char16_t* ctag16 = tree.getElementName(&len);
560 if (ctag16 == NULL) {
561 *outError = "failed to get XML element name (bad string pool)";
562 return Vector<String8>();
563 }
564 String8 tag(ctag16);
Adam Lesinski94fc9122013-09-30 17:16:09 -0700565
566 if (depth == 1) {
567 if (tag == serviceTagName) {
568 withinApduService = true;
569 }
570 } else if (depth == 2 && withinApduService) {
571 if (tag == "aid-group") {
572 String8 category = getAttribute(tree, CATEGORY_ATTR, &error);
573 if (error != "") {
574 if (outError != NULL) *outError = error;
575 return Vector<String8>();
576 }
577
578 categories.add(category);
579 }
580 }
581 }
582 }
583 aidAsset->close();
584 return categories;
585}
586
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700587static void printComponentPresence(const char* componentName) {
588 printf("provides-component:'%s'\n", componentName);
589}
590
Adam Lesinski282e1812014-01-23 18:17:42 -0800591/*
592 * Handle the "dump" command, to extract select data from an archive.
593 */
594extern char CONSOLE_DATA[2925]; // see EOF
595int doDump(Bundle* bundle)
596{
597 status_t result = UNKNOWN_ERROR;
598 Asset* asset = NULL;
599
600 if (bundle->getFileSpecCount() < 1) {
601 fprintf(stderr, "ERROR: no dump option specified\n");
602 return 1;
603 }
604
605 if (bundle->getFileSpecCount() < 2) {
606 fprintf(stderr, "ERROR: no dump file specified\n");
607 return 1;
608 }
609
610 const char* option = bundle->getFileSpecEntry(0);
611 const char* filename = bundle->getFileSpecEntry(1);
612
613 AssetManager assets;
Narayan Kamathf85e41f2014-01-24 14:14:30 +0000614 int32_t assetsCookie;
Adam Lesinski282e1812014-01-23 18:17:42 -0800615 if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
616 fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
617 return 1;
618 }
619
620 // Make a dummy config for retrieving resources... we need to supply
621 // non-default values for some configs so that we can retrieve resources
622 // in the app that don't have a default. The most important of these is
623 // the API version because key resources like icons will have an implicit
624 // version if they are using newer config types like density.
625 ResTable_config config;
Narayan Kamath91447d82014-01-21 15:32:36 +0000626 memset(&config, 0, sizeof(ResTable_config));
Adam Lesinski282e1812014-01-23 18:17:42 -0800627 config.language[0] = 'e';
628 config.language[1] = 'n';
629 config.country[0] = 'U';
630 config.country[1] = 'S';
631 config.orientation = ResTable_config::ORIENTATION_PORT;
632 config.density = ResTable_config::DENSITY_MEDIUM;
633 config.sdkVersion = 10000; // Very high.
634 config.screenWidthDp = 320;
635 config.screenHeightDp = 480;
636 config.smallestScreenWidthDp = 320;
637 assets.setConfiguration(config);
638
639 const ResTable& res = assets.getResources(false);
640 if (&res == NULL) {
641 fprintf(stderr, "ERROR: dump failed because no resource table was found\n");
642 goto bail;
Adam Lesinski25e9d552014-05-19 15:01:43 -0700643 } else if (res.getError() != NO_ERROR) {
644 fprintf(stderr, "ERROR: dump failed because the resource table is invalid/corrupt.\n");
645 goto bail;
Adam Lesinski282e1812014-01-23 18:17:42 -0800646 }
647
648 if (strcmp("resources", option) == 0) {
649#ifndef HAVE_ANDROID_OS
650 res.print(bundle->getValues());
651#endif
652
653 } else if (strcmp("strings", option) == 0) {
654 const ResStringPool* pool = res.getTableStringBlock(0);
655 printStringPool(pool);
656
657 } else if (strcmp("xmltree", option) == 0) {
658 if (bundle->getFileSpecCount() < 3) {
659 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
660 goto bail;
661 }
662
663 for (int i=2; i<bundle->getFileSpecCount(); i++) {
664 const char* resname = bundle->getFileSpecEntry(i);
665 ResXMLTree tree;
666 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
667 if (asset == NULL) {
668 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
669 goto bail;
670 }
671
672 if (tree.setTo(asset->getBuffer(true),
673 asset->getLength()) != NO_ERROR) {
674 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
675 goto bail;
676 }
677 tree.restart();
678 printXMLBlock(&tree);
679 tree.uninit();
680 delete asset;
681 asset = NULL;
682 }
683
684 } else if (strcmp("xmlstrings", option) == 0) {
685 if (bundle->getFileSpecCount() < 3) {
686 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
687 goto bail;
688 }
689
690 for (int i=2; i<bundle->getFileSpecCount(); i++) {
691 const char* resname = bundle->getFileSpecEntry(i);
692 ResXMLTree tree;
693 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
694 if (asset == NULL) {
695 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
696 goto bail;
697 }
698
699 if (tree.setTo(asset->getBuffer(true),
700 asset->getLength()) != NO_ERROR) {
701 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
702 goto bail;
703 }
704 printStringPool(&tree.getStrings());
705 delete asset;
706 asset = NULL;
707 }
708
709 } else {
710 ResXMLTree tree;
711 asset = assets.openNonAsset("AndroidManifest.xml",
712 Asset::ACCESS_BUFFER);
713 if (asset == NULL) {
714 fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
715 goto bail;
716 }
717
718 if (tree.setTo(asset->getBuffer(true),
719 asset->getLength()) != NO_ERROR) {
720 fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
721 goto bail;
722 }
723 tree.restart();
724
725 if (strcmp("permissions", option) == 0) {
726 size_t len;
727 ResXMLTree::event_code_t code;
728 int depth = 0;
729 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
730 if (code == ResXMLTree::END_TAG) {
731 depth--;
732 continue;
733 }
734 if (code != ResXMLTree::START_TAG) {
735 continue;
736 }
737 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700738 const char16_t* ctag16 = tree.getElementName(&len);
739 if (ctag16 == NULL) {
740 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
741 goto bail;
742 }
743 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -0800744 //printf("Depth %d tag %s\n", depth, tag.string());
745 if (depth == 1) {
746 if (tag != "manifest") {
747 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
748 goto bail;
749 }
750 String8 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -0700751 printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800752 } else if (depth == 2 && tag == "permission") {
753 String8 error;
754 String8 name = getAttribute(tree, NAME_ATTR, &error);
755 if (error != "") {
756 fprintf(stderr, "ERROR: %s\n", error.string());
757 goto bail;
758 }
Maurice Chu2675f762013-10-22 17:33:11 -0700759 printf("permission: %s\n",
760 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800761 } else if (depth == 2 && tag == "uses-permission") {
762 String8 error;
763 String8 name = getAttribute(tree, NAME_ATTR, &error);
764 if (error != "") {
765 fprintf(stderr, "ERROR: %s\n", error.string());
766 goto bail;
767 }
Adam Lesinski58f1f362013-11-12 12:59:08 -0800768 printUsesPermission(name,
769 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
770 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
Adam Lesinski282e1812014-01-23 18:17:42 -0800771 }
772 }
773 } else if (strcmp("badging", option) == 0) {
774 Vector<String8> locales;
775 res.getLocales(&locales);
776
777 Vector<ResTable_config> configs;
778 res.getConfigurations(&configs);
779 SortedVector<int> densities;
780 const size_t NC = configs.size();
781 for (size_t i=0; i<NC; i++) {
782 int dens = configs[i].density;
783 if (dens == 0) {
784 dens = 160;
785 }
786 densities.add(dens);
787 }
788
789 size_t len;
790 ResXMLTree::event_code_t code;
791 int depth = 0;
792 String8 error;
793 bool withinActivity = false;
794 bool isMainActivity = false;
795 bool isLauncherActivity = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800796 bool isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800797 bool isSearchable = false;
798 bool withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700799 bool withinSupportsInput = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800800 bool withinReceiver = false;
801 bool withinService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700802 bool withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800803 bool withinIntentFilter = false;
804 bool hasMainActivity = false;
805 bool hasOtherActivities = false;
806 bool hasOtherReceivers = false;
807 bool hasOtherServices = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700808 bool hasIntentFilter = false;
809
Adam Lesinski282e1812014-01-23 18:17:42 -0800810 bool hasWallpaperService = false;
811 bool hasImeService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700812 bool hasAccessibilityService = false;
813 bool hasPrintService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800814 bool hasWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700815 bool hasDeviceAdminReceiver = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700816 bool hasPaymentService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700817 bool hasDocumentsProvider = false;
818 bool hasCameraActivity = false;
819 bool hasCameraSecureActivity = false;
820 bool hasLauncher = false;
821 bool hasNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400822 bool hasDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700823
Adam Lesinski282e1812014-01-23 18:17:42 -0800824 bool actMainActivity = false;
825 bool actWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700826 bool actDeviceAdminEnabled = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800827 bool actImeService = false;
828 bool actWallpaperService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700829 bool actAccessibilityService = false;
830 bool actPrintService = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700831 bool actHostApduService = false;
832 bool actOffHostApduService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700833 bool actDocumentsProvider = false;
834 bool actNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400835 bool actDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700836 bool actCamera = false;
837 bool actCameraSecure = false;
838 bool catLauncher = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700839 bool hasMetaHostPaymentCategory = false;
840 bool hasMetaOffHostPaymentCategory = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700841
842 // These permissions are required by services implementing services
843 // the system binds to (IME, Accessibility, PrintServices, etc.)
844 bool hasBindDeviceAdminPermission = false;
845 bool hasBindInputMethodPermission = false;
846 bool hasBindAccessibilityServicePermission = false;
847 bool hasBindPrintServicePermission = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700848 bool hasBindNfcServicePermission = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700849 bool hasRequiredSafAttributes = false;
850 bool hasBindNotificationListenerServicePermission = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400851 bool hasBindDreamServicePermission = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800852
853 // These two implement the implicit permissions that are granted
854 // to pre-1.6 applications.
855 bool hasWriteExternalStoragePermission = false;
856 bool hasReadPhoneStatePermission = false;
857
858 // If an app requests write storage, they will also get read storage.
859 bool hasReadExternalStoragePermission = false;
860
861 // Implement transition to read and write call log.
862 bool hasReadContactsPermission = false;
863 bool hasWriteContactsPermission = false;
864 bool hasReadCallLogPermission = false;
865 bool hasWriteCallLogPermission = false;
866
867 // This next group of variables is used to implement a group of
868 // backward-compatibility heuristics necessitated by the addition of
869 // some new uses-feature constants in 2.1 and 2.2. In most cases, the
870 // heuristic is "if an app requests a permission but doesn't explicitly
871 // request the corresponding <uses-feature>, presume it's there anyway".
872 bool specCameraFeature = false; // camera-related
873 bool specCameraAutofocusFeature = false;
874 bool reqCameraAutofocusFeature = false;
875 bool reqCameraFlashFeature = false;
876 bool hasCameraPermission = false;
877 bool specLocationFeature = false; // location-related
878 bool specNetworkLocFeature = false;
879 bool reqNetworkLocFeature = false;
880 bool specGpsFeature = false;
881 bool reqGpsFeature = false;
882 bool hasMockLocPermission = false;
883 bool hasCoarseLocPermission = false;
884 bool hasGpsPermission = false;
885 bool hasGeneralLocPermission = false;
886 bool specBluetoothFeature = false; // Bluetooth API-related
887 bool hasBluetoothPermission = false;
888 bool specMicrophoneFeature = false; // microphone-related
889 bool hasRecordAudioPermission = false;
890 bool specWiFiFeature = false;
891 bool hasWiFiPermission = false;
892 bool specTelephonyFeature = false; // telephony-related
893 bool reqTelephonySubFeature = false;
894 bool hasTelephonyPermission = false;
895 bool specTouchscreenFeature = false; // touchscreen-related
896 bool specMultitouchFeature = false;
897 bool reqDistinctMultitouchFeature = false;
898 bool specScreenPortraitFeature = false;
899 bool specScreenLandscapeFeature = false;
900 bool reqScreenPortraitFeature = false;
901 bool reqScreenLandscapeFeature = false;
902 // 2.2 also added some other features that apps can request, but that
903 // have no corresponding permission, so we cannot implement any
904 // back-compatibility heuristic for them. The below are thus unnecessary
905 // (but are retained here for documentary purposes.)
906 //bool specCompassFeature = false;
907 //bool specAccelerometerFeature = false;
908 //bool specProximityFeature = false;
909 //bool specAmbientLightFeature = false;
910 //bool specLiveWallpaperFeature = false;
911
912 int targetSdk = 0;
913 int smallScreen = 1;
914 int normalScreen = 1;
915 int largeScreen = 1;
916 int xlargeScreen = 1;
917 int anyDensity = 1;
918 int requiresSmallestWidthDp = 0;
919 int compatibleWidthLimitDp = 0;
920 int largestWidthLimitDp = 0;
921 String8 pkg;
922 String8 activityName;
923 String8 activityLabel;
924 String8 activityIcon;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800925 String8 activityBanner;
Adam Lesinski282e1812014-01-23 18:17:42 -0800926 String8 receiverName;
927 String8 serviceName;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700928 Vector<String8> supportedInput;
Adam Lesinski282e1812014-01-23 18:17:42 -0800929 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
930 if (code == ResXMLTree::END_TAG) {
931 depth--;
932 if (depth < 2) {
Michael Wrightec4fdec2013-09-06 16:50:52 -0700933 if (withinSupportsInput && !supportedInput.isEmpty()) {
934 printf("supports-input: '");
935 const size_t N = supportedInput.size();
936 for (size_t i=0; i<N; i++) {
Maurice Chu2675f762013-10-22 17:33:11 -0700937 printf("%s", ResTable::normalizeForOutput(
938 supportedInput[i].string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -0700939 if (i != N - 1) {
940 printf("' '");
941 } else {
942 printf("'\n");
943 }
944 }
945 supportedInput.clear();
946 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800947 withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700948 withinSupportsInput = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800949 } else if (depth < 3) {
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800950 if (withinActivity && isMainActivity) {
Maurice Chu2675f762013-10-22 17:33:11 -0700951 String8 aName(getComponentName(pkg, activityName));
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800952 if (isLauncherActivity) {
953 printf("launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -0800954 if (aName.length() > 0) {
955 printf(" name='%s' ",
956 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800957 }
958 printf(" label='%s' icon='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -0800959 ResTable::normalizeForOutput(activityLabel.string()).string(),
960 ResTable::normalizeForOutput(activityIcon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800961 }
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800962 if (isLeanbackLauncherActivity) {
963 printf("leanback-launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -0800964 if (aName.length() > 0) {
965 printf(" name='%s' ",
966 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800967 }
968 printf(" label='%s' icon='%s' banner='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -0800969 ResTable::normalizeForOutput(activityLabel.string()).string(),
970 ResTable::normalizeForOutput(activityIcon.string()).string(),
971 ResTable::normalizeForOutput(activityBanner.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800972 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800973 }
974 if (!hasIntentFilter) {
975 hasOtherActivities |= withinActivity;
976 hasOtherReceivers |= withinReceiver;
977 hasOtherServices |= withinService;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700978 } else {
979 if (withinService) {
980 hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
981 hasBindNfcServicePermission);
982 hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
983 hasBindNfcServicePermission);
984 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800985 }
986 withinActivity = false;
987 withinService = false;
988 withinReceiver = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700989 withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800990 hasIntentFilter = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800991 isMainActivity = isLauncherActivity = isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800992 } else if (depth < 4) {
993 if (withinIntentFilter) {
994 if (withinActivity) {
995 hasMainActivity |= actMainActivity;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700996 hasLauncher |= catLauncher;
997 hasCameraActivity |= actCamera;
998 hasCameraSecureActivity |= actCameraSecure;
999 hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure;
Adam Lesinski282e1812014-01-23 18:17:42 -08001000 } else if (withinReceiver) {
1001 hasWidgetReceivers |= actWidgetReceivers;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001002 hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
1003 hasBindDeviceAdminPermission);
1004 hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
Adam Lesinski282e1812014-01-23 18:17:42 -08001005 } else if (withinService) {
1006 hasImeService |= actImeService;
1007 hasWallpaperService |= actWallpaperService;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001008 hasAccessibilityService |= (actAccessibilityService &&
1009 hasBindAccessibilityServicePermission);
1010 hasPrintService |= (actPrintService && hasBindPrintServicePermission);
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001011 hasNotificationListenerService |= actNotificationListenerService &&
1012 hasBindNotificationListenerServicePermission;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001013 hasDreamService |= actDreamService && hasBindDreamServicePermission;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001014 hasOtherServices |= (!actImeService && !actWallpaperService &&
Adam Lesinski94fc9122013-09-30 17:16:09 -07001015 !actAccessibilityService && !actPrintService &&
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001016 !actHostApduService && !actOffHostApduService &&
1017 !actNotificationListenerService);
1018 } else if (withinProvider) {
1019 hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes;
Adam Lesinski282e1812014-01-23 18:17:42 -08001020 }
1021 }
1022 withinIntentFilter = false;
1023 }
1024 continue;
1025 }
1026 if (code != ResXMLTree::START_TAG) {
1027 continue;
1028 }
1029 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001030
1031 const char16_t* ctag16 = tree.getElementName(&len);
1032 if (ctag16 == NULL) {
1033 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
1034 goto bail;
1035 }
1036 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -08001037 //printf("Depth %d, %s\n", depth, tag.string());
1038 if (depth == 1) {
1039 if (tag != "manifest") {
1040 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
1041 goto bail;
1042 }
1043 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -07001044 printf("package: name='%s' ",
1045 ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001046 int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
1047 if (error != "") {
1048 fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
1049 goto bail;
1050 }
1051 if (versionCode > 0) {
1052 printf("versionCode='%d' ", versionCode);
1053 } else {
1054 printf("versionCode='' ");
1055 }
1056 String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
1057 if (error != "") {
1058 fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
1059 goto bail;
1060 }
Maurice Chu2675f762013-10-22 17:33:11 -07001061 printf("versionName='%s'\n",
1062 ResTable::normalizeForOutput(versionName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001063 } else if (depth == 2) {
1064 withinApplication = false;
1065 if (tag == "application") {
1066 withinApplication = true;
1067
1068 String8 label;
1069 const size_t NL = locales.size();
1070 for (size_t i=0; i<NL; i++) {
1071 const char* localeStr = locales[i].string();
1072 assets.setLocale(localeStr != NULL ? localeStr : "");
1073 String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1074 if (llabel != "") {
1075 if (localeStr == NULL || strlen(localeStr) == 0) {
1076 label = llabel;
Maurice Chu2675f762013-10-22 17:33:11 -07001077 printf("application-label:'%s'\n",
1078 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001079 } else {
1080 if (label == "") {
1081 label = llabel;
1082 }
1083 printf("application-label-%s:'%s'\n", localeStr,
Maurice Chu2675f762013-10-22 17:33:11 -07001084 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001085 }
1086 }
1087 }
1088
1089 ResTable_config tmpConfig = config;
1090 const size_t ND = densities.size();
1091 for (size_t i=0; i<ND; i++) {
1092 tmpConfig.density = densities[i];
1093 assets.setConfiguration(tmpConfig);
1094 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1095 if (icon != "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001096 printf("application-icon-%d:'%s'\n", densities[i],
1097 ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001098 }
1099 }
1100 assets.setConfiguration(config);
1101
1102 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1103 if (error != "") {
1104 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
1105 goto bail;
1106 }
1107 int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
1108 if (error != "") {
1109 fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
1110 goto bail;
1111 }
Maurice Chu2675f762013-10-22 17:33:11 -07001112 printf("application: label='%s' ",
1113 ResTable::normalizeForOutput(label.string()).string());
1114 printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001115 if (testOnly != 0) {
1116 printf("testOnly='%d'\n", testOnly);
1117 }
1118
1119 int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
1120 if (error != "") {
1121 fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
1122 goto bail;
1123 }
1124 if (debuggable != 0) {
1125 printf("application-debuggable\n");
1126 }
1127 } else if (tag == "uses-sdk") {
1128 int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
1129 if (error != "") {
1130 error = "";
1131 String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
1132 if (error != "") {
1133 fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
1134 error.string());
1135 goto bail;
1136 }
1137 if (name == "Donut") targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001138 printf("sdkVersion:'%s'\n",
1139 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001140 } else if (code != -1) {
1141 targetSdk = code;
1142 printf("sdkVersion:'%d'\n", code);
1143 }
1144 code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
1145 if (code != -1) {
1146 printf("maxSdkVersion:'%d'\n", code);
1147 }
1148 code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
1149 if (error != "") {
1150 error = "";
1151 String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
1152 if (error != "") {
1153 fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
1154 error.string());
1155 goto bail;
1156 }
1157 if (name == "Donut" && targetSdk < 4) targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001158 printf("targetSdkVersion:'%s'\n",
1159 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001160 } else if (code != -1) {
1161 if (targetSdk < code) {
1162 targetSdk = code;
1163 }
1164 printf("targetSdkVersion:'%d'\n", code);
1165 }
1166 } else if (tag == "uses-configuration") {
1167 int32_t reqTouchScreen = getIntegerAttribute(tree,
1168 REQ_TOUCH_SCREEN_ATTR, NULL, 0);
1169 int32_t reqKeyboardType = getIntegerAttribute(tree,
1170 REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
1171 int32_t reqHardKeyboard = getIntegerAttribute(tree,
1172 REQ_HARD_KEYBOARD_ATTR, NULL, 0);
1173 int32_t reqNavigation = getIntegerAttribute(tree,
1174 REQ_NAVIGATION_ATTR, NULL, 0);
1175 int32_t reqFiveWayNav = getIntegerAttribute(tree,
1176 REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
1177 printf("uses-configuration:");
1178 if (reqTouchScreen != 0) {
1179 printf(" reqTouchScreen='%d'", reqTouchScreen);
1180 }
1181 if (reqKeyboardType != 0) {
1182 printf(" reqKeyboardType='%d'", reqKeyboardType);
1183 }
1184 if (reqHardKeyboard != 0) {
1185 printf(" reqHardKeyboard='%d'", reqHardKeyboard);
1186 }
1187 if (reqNavigation != 0) {
1188 printf(" reqNavigation='%d'", reqNavigation);
1189 }
1190 if (reqFiveWayNav != 0) {
1191 printf(" reqFiveWayNav='%d'", reqFiveWayNav);
1192 }
1193 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001194 } else if (tag == "supports-input") {
1195 withinSupportsInput = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001196 } else if (tag == "supports-screens") {
1197 smallScreen = getIntegerAttribute(tree,
1198 SMALL_SCREEN_ATTR, NULL, 1);
1199 normalScreen = getIntegerAttribute(tree,
1200 NORMAL_SCREEN_ATTR, NULL, 1);
1201 largeScreen = getIntegerAttribute(tree,
1202 LARGE_SCREEN_ATTR, NULL, 1);
1203 xlargeScreen = getIntegerAttribute(tree,
1204 XLARGE_SCREEN_ATTR, NULL, 1);
1205 anyDensity = getIntegerAttribute(tree,
1206 ANY_DENSITY_ATTR, NULL, 1);
1207 requiresSmallestWidthDp = getIntegerAttribute(tree,
1208 REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
1209 compatibleWidthLimitDp = getIntegerAttribute(tree,
1210 COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1211 largestWidthLimitDp = getIntegerAttribute(tree,
1212 LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1213 } else if (tag == "uses-feature") {
1214 String8 name = getAttribute(tree, NAME_ATTR, &error);
1215
1216 if (name != "" && error == "") {
1217 int req = getIntegerAttribute(tree,
1218 REQUIRED_ATTR, NULL, 1);
1219
1220 if (name == "android.hardware.camera") {
1221 specCameraFeature = true;
1222 } else if (name == "android.hardware.camera.autofocus") {
1223 // these have no corresponding permission to check for,
1224 // but should imply the foundational camera permission
1225 reqCameraAutofocusFeature = reqCameraAutofocusFeature || req;
1226 specCameraAutofocusFeature = true;
1227 } else if (req && (name == "android.hardware.camera.flash")) {
1228 // these have no corresponding permission to check for,
1229 // but should imply the foundational camera permission
1230 reqCameraFlashFeature = true;
1231 } else if (name == "android.hardware.location") {
1232 specLocationFeature = true;
1233 } else if (name == "android.hardware.location.network") {
1234 specNetworkLocFeature = true;
1235 reqNetworkLocFeature = reqNetworkLocFeature || req;
1236 } else if (name == "android.hardware.location.gps") {
1237 specGpsFeature = true;
1238 reqGpsFeature = reqGpsFeature || req;
1239 } else if (name == "android.hardware.bluetooth") {
1240 specBluetoothFeature = true;
1241 } else if (name == "android.hardware.touchscreen") {
1242 specTouchscreenFeature = true;
1243 } else if (name == "android.hardware.touchscreen.multitouch") {
1244 specMultitouchFeature = true;
1245 } else if (name == "android.hardware.touchscreen.multitouch.distinct") {
1246 reqDistinctMultitouchFeature = reqDistinctMultitouchFeature || req;
1247 } else if (name == "android.hardware.microphone") {
1248 specMicrophoneFeature = true;
1249 } else if (name == "android.hardware.wifi") {
1250 specWiFiFeature = true;
1251 } else if (name == "android.hardware.telephony") {
1252 specTelephonyFeature = true;
1253 } else if (req && (name == "android.hardware.telephony.gsm" ||
1254 name == "android.hardware.telephony.cdma")) {
1255 // these have no corresponding permission to check for,
1256 // but should imply the foundational telephony permission
1257 reqTelephonySubFeature = true;
1258 } else if (name == "android.hardware.screen.portrait") {
1259 specScreenPortraitFeature = true;
1260 } else if (name == "android.hardware.screen.landscape") {
1261 specScreenLandscapeFeature = true;
1262 }
1263 printf("uses-feature%s:'%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001264 req ? "" : "-not-required",
1265 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001266 } else {
1267 int vers = getIntegerAttribute(tree,
1268 GL_ES_VERSION_ATTR, &error);
1269 if (error == "") {
1270 printf("uses-gl-es:'0x%x'\n", vers);
1271 }
1272 }
1273 } else if (tag == "uses-permission") {
1274 String8 name = getAttribute(tree, NAME_ATTR, &error);
1275 if (name != "" && error == "") {
1276 if (name == "android.permission.CAMERA") {
1277 hasCameraPermission = true;
1278 } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
1279 hasGpsPermission = true;
1280 } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
1281 hasMockLocPermission = true;
1282 } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
1283 hasCoarseLocPermission = true;
1284 } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
1285 name == "android.permission.INSTALL_LOCATION_PROVIDER") {
1286 hasGeneralLocPermission = true;
1287 } else if (name == "android.permission.BLUETOOTH" ||
1288 name == "android.permission.BLUETOOTH_ADMIN") {
1289 hasBluetoothPermission = true;
1290 } else if (name == "android.permission.RECORD_AUDIO") {
1291 hasRecordAudioPermission = true;
1292 } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
1293 name == "android.permission.CHANGE_WIFI_STATE" ||
1294 name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
1295 hasWiFiPermission = true;
1296 } else if (name == "android.permission.CALL_PHONE" ||
1297 name == "android.permission.CALL_PRIVILEGED" ||
1298 name == "android.permission.MODIFY_PHONE_STATE" ||
1299 name == "android.permission.PROCESS_OUTGOING_CALLS" ||
1300 name == "android.permission.READ_SMS" ||
1301 name == "android.permission.RECEIVE_SMS" ||
1302 name == "android.permission.RECEIVE_MMS" ||
1303 name == "android.permission.RECEIVE_WAP_PUSH" ||
1304 name == "android.permission.SEND_SMS" ||
1305 name == "android.permission.WRITE_APN_SETTINGS" ||
1306 name == "android.permission.WRITE_SMS") {
1307 hasTelephonyPermission = true;
1308 } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
1309 hasWriteExternalStoragePermission = true;
1310 } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
1311 hasReadExternalStoragePermission = true;
1312 } else if (name == "android.permission.READ_PHONE_STATE") {
1313 hasReadPhoneStatePermission = true;
1314 } else if (name == "android.permission.READ_CONTACTS") {
1315 hasReadContactsPermission = true;
1316 } else if (name == "android.permission.WRITE_CONTACTS") {
1317 hasWriteContactsPermission = true;
1318 } else if (name == "android.permission.READ_CALL_LOG") {
1319 hasReadCallLogPermission = true;
1320 } else if (name == "android.permission.WRITE_CALL_LOG") {
1321 hasWriteCallLogPermission = true;
1322 }
Adam Lesinski58f1f362013-11-12 12:59:08 -08001323
1324 printUsesPermission(name,
1325 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
1326 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
1327 } else {
Adam Lesinski282e1812014-01-23 18:17:42 -08001328 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1329 error.string());
1330 goto bail;
1331 }
1332 } else if (tag == "uses-package") {
1333 String8 name = getAttribute(tree, NAME_ATTR, &error);
1334 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001335 printf("uses-package:'%s'\n",
1336 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001337 } else {
1338 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1339 error.string());
1340 goto bail;
1341 }
1342 } else if (tag == "original-package") {
1343 String8 name = getAttribute(tree, NAME_ATTR, &error);
1344 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001345 printf("original-package:'%s'\n",
1346 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001347 } else {
1348 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1349 error.string());
1350 goto bail;
1351 }
1352 } else if (tag == "supports-gl-texture") {
1353 String8 name = getAttribute(tree, NAME_ATTR, &error);
1354 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001355 printf("supports-gl-texture:'%s'\n",
1356 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001357 } else {
1358 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1359 error.string());
1360 goto bail;
1361 }
1362 } else if (tag == "compatible-screens") {
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001363 printCompatibleScreens(tree, &error);
1364 if (error != "") {
1365 fprintf(stderr, "ERROR getting compatible screens: %s\n",
1366 error.string());
1367 goto bail;
1368 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001369 depth--;
1370 } else if (tag == "package-verifier") {
1371 String8 name = getAttribute(tree, NAME_ATTR, &error);
1372 if (name != "" && error == "") {
1373 String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
1374 if (publicKey != "" && error == "") {
1375 printf("package-verifier: name='%s' publicKey='%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001376 ResTable::normalizeForOutput(name.string()).string(),
1377 ResTable::normalizeForOutput(publicKey.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001378 }
1379 }
1380 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001381 } else if (depth == 3) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001382 withinActivity = false;
1383 withinReceiver = false;
1384 withinService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001385 withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001386 hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001387 hasMetaHostPaymentCategory = false;
1388 hasMetaOffHostPaymentCategory = false;
1389 hasBindDeviceAdminPermission = false;
1390 hasBindInputMethodPermission = false;
1391 hasBindAccessibilityServicePermission = false;
1392 hasBindPrintServicePermission = false;
1393 hasBindNfcServicePermission = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001394 hasRequiredSafAttributes = false;
1395 hasBindNotificationListenerServicePermission = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001396 hasBindDreamServicePermission = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001397 if (withinApplication) {
1398 if(tag == "activity") {
1399 withinActivity = true;
1400 activityName = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001401 if (error != "") {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001402 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1403 error.string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001404 goto bail;
1405 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001406
Michael Wrightec4fdec2013-09-06 16:50:52 -07001407 activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1408 if (error != "") {
1409 fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
1410 error.string());
1411 goto bail;
1412 }
1413
1414 activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1415 if (error != "") {
1416 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
1417 error.string());
1418 goto bail;
1419 }
1420
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001421 activityBanner = getResolvedAttribute(&res, tree, BANNER_ATTR, &error);
1422 if (error != "") {
1423 fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
1424 error.string());
1425 goto bail;
1426 }
1427
Michael Wrightec4fdec2013-09-06 16:50:52 -07001428 int32_t orien = getResolvedIntegerAttribute(&res, tree,
1429 SCREEN_ORIENTATION_ATTR, &error);
1430 if (error == "") {
1431 if (orien == 0 || orien == 6 || orien == 8) {
1432 // Requests landscape, sensorLandscape, or reverseLandscape.
1433 reqScreenLandscapeFeature = true;
1434 } else if (orien == 1 || orien == 7 || orien == 9) {
1435 // Requests portrait, sensorPortrait, or reversePortrait.
1436 reqScreenPortraitFeature = true;
1437 }
1438 }
1439 } else if (tag == "uses-library") {
1440 String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
1441 if (error != "") {
1442 fprintf(stderr,
1443 "ERROR getting 'android:name' attribute for uses-library"
1444 " %s\n", error.string());
1445 goto bail;
1446 }
1447 int req = getIntegerAttribute(tree,
1448 REQUIRED_ATTR, NULL, 1);
1449 printf("uses-library%s:'%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001450 req ? "" : "-not-required", ResTable::normalizeForOutput(
1451 libraryName.string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001452 } else if (tag == "receiver") {
1453 withinReceiver = true;
1454 receiverName = getAttribute(tree, NAME_ATTR, &error);
1455
1456 if (error != "") {
1457 fprintf(stderr,
1458 "ERROR getting 'android:name' attribute for receiver:"
1459 " %s\n", error.string());
1460 goto bail;
1461 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001462
1463 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1464 if (error == "") {
1465 if (permission == "android.permission.BIND_DEVICE_ADMIN") {
1466 hasBindDeviceAdminPermission = true;
1467 }
1468 } else {
1469 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1470 " receiver '%s': %s\n", receiverName.string(), error.string());
1471 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001472 } else if (tag == "service") {
1473 withinService = true;
1474 serviceName = getAttribute(tree, NAME_ATTR, &error);
1475
1476 if (error != "") {
1477 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1478 "service:%s\n", error.string());
1479 goto bail;
1480 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001481
1482 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1483 if (error == "") {
1484 if (permission == "android.permission.BIND_INPUT_METHOD") {
1485 hasBindInputMethodPermission = true;
1486 } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
1487 hasBindAccessibilityServicePermission = true;
1488 } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
1489 hasBindPrintServicePermission = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001490 } else if (permission == "android.permission.BIND_NFC_SERVICE") {
1491 hasBindNfcServicePermission = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001492 } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
1493 hasBindNotificationListenerServicePermission = true;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001494 } else if (permission == "android.permission.BIND_DREAM_SERVICE") {
1495 hasBindDreamServicePermission = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001496 }
1497 } else {
1498 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1499 " service '%s': %s\n", serviceName.string(), error.string());
1500 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001501 } else if (tag == "provider") {
1502 withinProvider = true;
1503
1504 bool exported = getResolvedIntegerAttribute(&res, tree, EXPORTED_ATTR, &error);
1505 if (error != "") {
1506 fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:"
1507 " %s\n", error.string());
1508 goto bail;
1509 }
1510
1511 bool grantUriPermissions = getResolvedIntegerAttribute(&res, tree,
1512 GRANT_URI_PERMISSIONS_ATTR, &error);
1513 if (error != "") {
1514 fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:"
1515 " %s\n", error.string());
1516 goto bail;
1517 }
1518
1519 String8 permission = getResolvedAttribute(&res, tree, PERMISSION_ATTR, &error);
1520 if (error != "") {
1521 fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:"
1522 " %s\n", error.string());
1523 goto bail;
1524 }
1525
1526 hasRequiredSafAttributes |= exported && grantUriPermissions &&
1527 permission == "android.permission.MANAGE_DOCUMENTS";
1528
Michael Wrightec4fdec2013-09-06 16:50:52 -07001529 } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
Adam Lesinskib71adb62014-05-15 14:14:41 -07001530 String8 metaDataName = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001531 if (error != "") {
1532 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1533 "meta-data:%s\n", error.string());
1534 goto bail;
1535 }
Maurice Chu2675f762013-10-22 17:33:11 -07001536 printf("meta-data: name='%s' ",
1537 ResTable::normalizeForOutput(metaDataName.string()).string());
Maurice Chu76327312013-10-16 18:28:46 -07001538 printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"),
1539 &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001540 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001541 // Try looking for a RESOURCE_ATTR
1542 error = "";
1543 printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR,
1544 String8("resource"), &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001545 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001546 fprintf(stderr, "ERROR getting 'android:value' or "
1547 "'android:resource' attribute for "
1548 "meta-data:%s\n", error.string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001549 goto bail;
1550 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001551 }
Maurice Chu76327312013-10-16 18:28:46 -07001552 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001553 } else if (withinSupportsInput && tag == "input-type") {
1554 String8 name = getAttribute(tree, NAME_ATTR, &error);
1555 if (name != "" && error == "") {
1556 supportedInput.add(name);
1557 } else {
1558 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1559 error.string());
1560 goto bail;
1561 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001562 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001563 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001564 } else if (depth == 4) {
1565 if (tag == "intent-filter") {
1566 hasIntentFilter = true;
1567 withinIntentFilter = true;
1568 actMainActivity = false;
1569 actWidgetReceivers = false;
1570 actImeService = false;
1571 actWallpaperService = false;
1572 actAccessibilityService = false;
1573 actPrintService = false;
1574 actDeviceAdminEnabled = false;
1575 actHostApduService = false;
1576 actOffHostApduService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001577 actDocumentsProvider = false;
1578 actNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001579 actDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001580 actCamera = false;
1581 actCameraSecure = false;
1582 catLauncher = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001583 } else if (withinService && tag == "meta-data") {
1584 String8 name = getAttribute(tree, NAME_ATTR, &error);
1585 if (error != "") {
1586 fprintf(stderr, "ERROR getting 'android:name' attribute for"
1587 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1588 goto bail;
1589 }
1590
1591 if (name == "android.nfc.cardemulation.host_apdu_service" ||
1592 name == "android.nfc.cardemulation.off_host_apdu_service") {
1593 bool offHost = true;
1594 if (name == "android.nfc.cardemulation.host_apdu_service") {
1595 offHost = false;
1596 }
1597
1598 String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
1599 if (error != "") {
1600 fprintf(stderr, "ERROR getting 'android:resource' attribute for"
1601 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1602 goto bail;
1603 }
1604
1605 Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
1606 offHost, &error);
1607 if (error != "") {
1608 fprintf(stderr, "ERROR getting AID category for service '%s'\n",
1609 serviceName.string());
1610 goto bail;
1611 }
1612
1613 const size_t catLen = categories.size();
1614 for (size_t i = 0; i < catLen; i++) {
1615 bool paymentCategory = (categories[i] == "payment");
1616 if (offHost) {
1617 hasMetaOffHostPaymentCategory |= paymentCategory;
1618 } else {
1619 hasMetaHostPaymentCategory |= paymentCategory;
1620 }
1621 }
1622 }
1623 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001624 } else if ((depth == 5) && withinIntentFilter) {
1625 String8 action;
1626 if (tag == "action") {
1627 action = getAttribute(tree, NAME_ATTR, &error);
1628 if (error != "") {
1629 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1630 error.string());
1631 goto bail;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001632 }
1633
Adam Lesinskia5018c92013-09-30 16:23:15 -07001634 if (withinActivity) {
1635 if (action == "android.intent.action.MAIN") {
1636 isMainActivity = true;
1637 actMainActivity = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001638 } else if (action == "android.media.action.STILL_IMAGE_CAMERA" ||
1639 action == "android.media.action.VIDEO_CAMERA") {
1640 actCamera = true;
1641 } else if (action == "android.media.action.STILL_IMAGE_CAMERA_SECURE") {
1642 actCameraSecure = true;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001643 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001644 } else if (withinReceiver) {
1645 if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
1646 actWidgetReceivers = true;
1647 } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
1648 actDeviceAdminEnabled = true;
1649 }
1650 } else if (withinService) {
1651 if (action == "android.view.InputMethod") {
1652 actImeService = true;
1653 } else if (action == "android.service.wallpaper.WallpaperService") {
1654 actWallpaperService = true;
1655 } else if (action == "android.accessibilityservice.AccessibilityService") {
1656 actAccessibilityService = true;
1657 } else if (action == "android.printservice.PrintService") {
1658 actPrintService = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001659 } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
1660 actHostApduService = true;
1661 } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
1662 actOffHostApduService = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001663 } else if (action == "android.service.notification.NotificationListenerService") {
1664 actNotificationListenerService = true;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001665 } else if (action == "android.service.dreams.DreamService") {
1666 actDreamService = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001667 }
1668 } else if (withinProvider) {
1669 if (action == "android.content.action.DOCUMENTS_PROVIDER") {
1670 actDocumentsProvider = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001671 }
1672 }
1673 if (action == "android.intent.action.SEARCH") {
1674 isSearchable = true;
1675 }
1676 }
1677
1678 if (tag == "category") {
1679 String8 category = getAttribute(tree, NAME_ATTR, &error);
1680 if (error != "") {
1681 fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
1682 error.string());
1683 goto bail;
1684 }
1685 if (withinActivity) {
1686 if (category == "android.intent.category.LAUNCHER") {
1687 isLauncherActivity = true;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001688 } else if (category == "android.intent.category.LEANBACK_LAUNCHER") {
1689 isLeanbackLauncherActivity = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001690 } else if (category == "android.intent.category.HOME") {
1691 catLauncher = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001692 }
1693 }
1694 }
1695 }
1696 }
1697
1698 // Pre-1.6 implicitly granted permission compatibility logic
1699 if (targetSdk < 4) {
1700 if (!hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001701 printUsesPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"));
1702 printUsesImpliedPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"),
1703 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001704 hasWriteExternalStoragePermission = true;
1705 }
1706 if (!hasReadPhoneStatePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001707 printUsesPermission(String8("android.permission.READ_PHONE_STATE"));
1708 printUsesImpliedPermission(String8("android.permission.READ_PHONE_STATE"),
1709 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001710 }
1711 }
1712
1713 // If the application has requested WRITE_EXTERNAL_STORAGE, we will
1714 // force them to always take READ_EXTERNAL_STORAGE as well. We always
1715 // do this (regardless of target API version) because we can't have
1716 // an app with write permission but not read permission.
1717 if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001718 printUsesPermission(String8("android.permission.READ_EXTERNAL_STORAGE"));
1719 printUsesImpliedPermission(String8("android.permission.READ_EXTERNAL_STORAGE"),
1720 String8("requested WRITE_EXTERNAL_STORAGE"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001721 }
1722
1723 // Pre-JellyBean call log permission compatibility.
1724 if (targetSdk < 16) {
1725 if (!hasReadCallLogPermission && hasReadContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001726 printUsesPermission(String8("android.permission.READ_CALL_LOG"));
1727 printUsesImpliedPermission(String8("android.permission.READ_CALL_LOG"),
1728 String8("targetSdkVersion < 16 and requested READ_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001729 }
1730 if (!hasWriteCallLogPermission && hasWriteContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001731 printUsesPermission(String8("android.permission.WRITE_CALL_LOG"));
1732 printUsesImpliedPermission(String8("android.permission.WRITE_CALL_LOG"),
1733 String8("targetSdkVersion < 16 and requested WRITE_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001734 }
1735 }
1736
1737 /* The following blocks handle printing "inferred" uses-features, based
1738 * on whether related features or permissions are used by the app.
1739 * Note that the various spec*Feature variables denote whether the
1740 * relevant tag was *present* in the AndroidManfest, not that it was
1741 * present and set to true.
1742 */
1743 // Camera-related back-compatibility logic
1744 if (!specCameraFeature) {
1745 if (reqCameraFlashFeature) {
1746 // if app requested a sub-feature (autofocus or flash) and didn't
1747 // request the base camera feature, we infer that it meant to
1748 printf("uses-feature:'android.hardware.camera'\n");
1749 printf("uses-implied-feature:'android.hardware.camera'," \
1750 "'requested android.hardware.camera.flash feature'\n");
1751 } else if (reqCameraAutofocusFeature) {
1752 // if app requested a sub-feature (autofocus or flash) and didn't
1753 // request the base camera feature, we infer that it meant to
1754 printf("uses-feature:'android.hardware.camera'\n");
1755 printf("uses-implied-feature:'android.hardware.camera'," \
1756 "'requested android.hardware.camera.autofocus feature'\n");
1757 } else if (hasCameraPermission) {
Maurice Chu2675f762013-10-22 17:33:11 -07001758 // if app wants to use camera but didn't request the feature, we infer
Adam Lesinski282e1812014-01-23 18:17:42 -08001759 // that it meant to, and further that it wants autofocus
1760 // (which was the 1.0 - 1.5 behavior)
1761 printf("uses-feature:'android.hardware.camera'\n");
1762 if (!specCameraAutofocusFeature) {
1763 printf("uses-feature:'android.hardware.camera.autofocus'\n");
1764 printf("uses-implied-feature:'android.hardware.camera.autofocus'," \
1765 "'requested android.permission.CAMERA permission'\n");
1766 }
1767 }
1768 }
1769
1770 // Location-related back-compatibility logic
1771 if (!specLocationFeature &&
1772 (hasMockLocPermission || hasCoarseLocPermission || hasGpsPermission ||
1773 hasGeneralLocPermission || reqNetworkLocFeature || reqGpsFeature)) {
1774 // if app either takes a location-related permission or requests one of the
1775 // sub-features, we infer that it also meant to request the base location feature
1776 printf("uses-feature:'android.hardware.location'\n");
1777 printf("uses-implied-feature:'android.hardware.location'," \
1778 "'requested a location access permission'\n");
1779 }
1780 if (!specGpsFeature && hasGpsPermission) {
1781 // if app takes GPS (FINE location) perm but does not request the GPS
1782 // feature, we infer that it meant to
1783 printf("uses-feature:'android.hardware.location.gps'\n");
1784 printf("uses-implied-feature:'android.hardware.location.gps'," \
1785 "'requested android.permission.ACCESS_FINE_LOCATION permission'\n");
1786 }
1787 if (!specNetworkLocFeature && hasCoarseLocPermission) {
1788 // if app takes Network location (COARSE location) perm but does not request the
1789 // network location feature, we infer that it meant to
1790 printf("uses-feature:'android.hardware.location.network'\n");
1791 printf("uses-implied-feature:'android.hardware.location.network'," \
1792 "'requested android.permission.ACCESS_COARSE_LOCATION permission'\n");
1793 }
1794
1795 // Bluetooth-related compatibility logic
1796 if (!specBluetoothFeature && hasBluetoothPermission && (targetSdk > 4)) {
1797 // if app takes a Bluetooth permission but does not request the Bluetooth
1798 // feature, we infer that it meant to
1799 printf("uses-feature:'android.hardware.bluetooth'\n");
1800 printf("uses-implied-feature:'android.hardware.bluetooth'," \
1801 "'requested android.permission.BLUETOOTH or android.permission.BLUETOOTH_ADMIN " \
1802 "permission and targetSdkVersion > 4'\n");
1803 }
1804
1805 // Microphone-related compatibility logic
1806 if (!specMicrophoneFeature && hasRecordAudioPermission) {
1807 // if app takes the record-audio permission but does not request the microphone
1808 // feature, we infer that it meant to
1809 printf("uses-feature:'android.hardware.microphone'\n");
1810 printf("uses-implied-feature:'android.hardware.microphone'," \
1811 "'requested android.permission.RECORD_AUDIO permission'\n");
1812 }
1813
1814 // WiFi-related compatibility logic
1815 if (!specWiFiFeature && hasWiFiPermission) {
1816 // if app takes one of the WiFi permissions but does not request the WiFi
1817 // feature, we infer that it meant to
1818 printf("uses-feature:'android.hardware.wifi'\n");
1819 printf("uses-implied-feature:'android.hardware.wifi'," \
1820 "'requested android.permission.ACCESS_WIFI_STATE, " \
1821 "android.permission.CHANGE_WIFI_STATE, or " \
1822 "android.permission.CHANGE_WIFI_MULTICAST_STATE permission'\n");
1823 }
1824
1825 // Telephony-related compatibility logic
1826 if (!specTelephonyFeature && (hasTelephonyPermission || reqTelephonySubFeature)) {
1827 // if app takes one of the telephony permissions or requests a sub-feature but
1828 // does not request the base telephony feature, we infer that it meant to
1829 printf("uses-feature:'android.hardware.telephony'\n");
1830 printf("uses-implied-feature:'android.hardware.telephony'," \
1831 "'requested a telephony-related permission or feature'\n");
1832 }
1833
1834 // Touchscreen-related back-compatibility logic
1835 if (!specTouchscreenFeature) { // not a typo!
1836 // all apps are presumed to require a touchscreen, unless they explicitly say
1837 // <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
1838 // Note that specTouchscreenFeature is true if the tag is present, regardless
1839 // of whether its value is true or false, so this is safe
1840 printf("uses-feature:'android.hardware.touchscreen'\n");
1841 printf("uses-implied-feature:'android.hardware.touchscreen'," \
1842 "'assumed you require a touch screen unless explicitly made optional'\n");
1843 }
1844 if (!specMultitouchFeature && reqDistinctMultitouchFeature) {
1845 // if app takes one of the telephony permissions or requests a sub-feature but
1846 // does not request the base telephony feature, we infer that it meant to
1847 printf("uses-feature:'android.hardware.touchscreen.multitouch'\n");
1848 printf("uses-implied-feature:'android.hardware.touchscreen.multitouch'," \
1849 "'requested android.hardware.touchscreen.multitouch.distinct feature'\n");
1850 }
1851
1852 // Landscape/portrait-related compatibility logic
1853 if (!specScreenLandscapeFeature && !specScreenPortraitFeature) {
1854 // If the app has specified any activities in its manifest
1855 // that request a specific orientation, then assume that
1856 // orientation is required.
1857 if (reqScreenLandscapeFeature) {
1858 printf("uses-feature:'android.hardware.screen.landscape'\n");
1859 printf("uses-implied-feature:'android.hardware.screen.landscape'," \
1860 "'one or more activities have specified a landscape orientation'\n");
1861 }
1862 if (reqScreenPortraitFeature) {
1863 printf("uses-feature:'android.hardware.screen.portrait'\n");
1864 printf("uses-implied-feature:'android.hardware.screen.portrait'," \
1865 "'one or more activities have specified a portrait orientation'\n");
1866 }
1867 }
1868
Adam Lesinski282e1812014-01-23 18:17:42 -08001869 if (hasWidgetReceivers) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001870 printComponentPresence("app-widget");
Adam Lesinski282e1812014-01-23 18:17:42 -08001871 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001872 if (hasDeviceAdminReceiver) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001873 printComponentPresence("device-admin");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001874 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001875 if (hasImeService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001876 printComponentPresence("ime");
Adam Lesinski282e1812014-01-23 18:17:42 -08001877 }
1878 if (hasWallpaperService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001879 printComponentPresence("wallpaper");
Adam Lesinski282e1812014-01-23 18:17:42 -08001880 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001881 if (hasAccessibilityService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001882 printComponentPresence("accessibility");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001883 }
1884 if (hasPrintService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001885 printComponentPresence("print-service");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001886 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001887 if (hasPaymentService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001888 printComponentPresence("payment");
1889 }
1890 if (isSearchable) {
1891 printComponentPresence("search");
1892 }
1893 if (hasDocumentsProvider) {
1894 printComponentPresence("document-provider");
1895 }
1896 if (hasLauncher) {
1897 printComponentPresence("launcher");
1898 }
1899 if (hasNotificationListenerService) {
1900 printComponentPresence("notification-listener");
1901 }
John Spurlockeb8d1be2014-06-25 17:46:15 -04001902 if (hasDreamService) {
1903 printComponentPresence("dream");
1904 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001905 if (hasCameraActivity) {
1906 printComponentPresence("camera");
1907 }
1908 if (hasCameraSecureActivity) {
1909 printComponentPresence("camera-secure");
1910 }
1911
1912 if (hasMainActivity) {
1913 printf("main\n");
Adam Lesinski94fc9122013-09-30 17:16:09 -07001914 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001915 if (hasOtherActivities) {
1916 printf("other-activities\n");
1917 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001918 if (hasOtherReceivers) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001919 printf("other-receivers\n");
1920 }
1921 if (hasOtherServices) {
1922 printf("other-services\n");
1923 }
1924
1925 // For modern apps, if screen size buckets haven't been specified
1926 // but the new width ranges have, then infer the buckets from them.
1927 if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0
1928 && requiresSmallestWidthDp > 0) {
1929 int compatWidth = compatibleWidthLimitDp;
1930 if (compatWidth <= 0) {
1931 compatWidth = requiresSmallestWidthDp;
1932 }
1933 if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) {
1934 smallScreen = -1;
1935 } else {
1936 smallScreen = 0;
1937 }
1938 if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) {
1939 normalScreen = -1;
1940 } else {
1941 normalScreen = 0;
1942 }
1943 if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) {
1944 largeScreen = -1;
1945 } else {
1946 largeScreen = 0;
1947 }
1948 if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) {
1949 xlargeScreen = -1;
1950 } else {
1951 xlargeScreen = 0;
1952 }
1953 }
1954
1955 // Determine default values for any unspecified screen sizes,
1956 // based on the target SDK of the package. As of 4 (donut)
1957 // the screen size support was introduced, so all default to
1958 // enabled.
1959 if (smallScreen > 0) {
1960 smallScreen = targetSdk >= 4 ? -1 : 0;
1961 }
1962 if (normalScreen > 0) {
1963 normalScreen = -1;
1964 }
1965 if (largeScreen > 0) {
1966 largeScreen = targetSdk >= 4 ? -1 : 0;
1967 }
1968 if (xlargeScreen > 0) {
1969 // Introduced in Gingerbread.
1970 xlargeScreen = targetSdk >= 9 ? -1 : 0;
1971 }
1972 if (anyDensity > 0) {
1973 anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0
1974 || compatibleWidthLimitDp > 0) ? -1 : 0;
1975 }
1976 printf("supports-screens:");
1977 if (smallScreen != 0) {
1978 printf(" 'small'");
1979 }
1980 if (normalScreen != 0) {
1981 printf(" 'normal'");
1982 }
1983 if (largeScreen != 0) {
1984 printf(" 'large'");
1985 }
1986 if (xlargeScreen != 0) {
1987 printf(" 'xlarge'");
1988 }
1989 printf("\n");
1990 printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
1991 if (requiresSmallestWidthDp > 0) {
1992 printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp);
1993 }
1994 if (compatibleWidthLimitDp > 0) {
1995 printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp);
1996 }
1997 if (largestWidthLimitDp > 0) {
1998 printf("largest-width-limit:'%d'\n", largestWidthLimitDp);
1999 }
2000
2001 printf("locales:");
2002 const size_t NL = locales.size();
2003 for (size_t i=0; i<NL; i++) {
2004 const char* localeStr = locales[i].string();
2005 if (localeStr == NULL || strlen(localeStr) == 0) {
2006 localeStr = "--_--";
2007 }
2008 printf(" '%s'", localeStr);
2009 }
2010 printf("\n");
2011
2012 printf("densities:");
2013 const size_t ND = densities.size();
2014 for (size_t i=0; i<ND; i++) {
2015 printf(" '%d'", densities[i]);
2016 }
2017 printf("\n");
2018
2019 AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
2020 if (dir != NULL) {
2021 if (dir->getFileCount() > 0) {
2022 printf("native-code:");
2023 for (size_t i=0; i<dir->getFileCount(); i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07002024 printf(" '%s'", ResTable::normalizeForOutput(
2025 dir->getFileName(i).string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002026 }
2027 printf("\n");
2028 }
2029 delete dir;
2030 }
2031 } else if (strcmp("badger", option) == 0) {
2032 printf("%s", CONSOLE_DATA);
2033 } else if (strcmp("configurations", option) == 0) {
2034 Vector<ResTable_config> configs;
2035 res.getConfigurations(&configs);
2036 const size_t N = configs.size();
2037 for (size_t i=0; i<N; i++) {
2038 printf("%s\n", configs[i].toString().string());
2039 }
2040 } else {
2041 fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
2042 goto bail;
2043 }
2044 }
2045
2046 result = NO_ERROR;
2047
2048bail:
2049 if (asset) {
2050 delete asset;
2051 }
2052 return (result != NO_ERROR);
2053}
2054
2055
2056/*
2057 * Handle the "add" command, which wants to add files to a new or
2058 * pre-existing archive.
2059 */
2060int doAdd(Bundle* bundle)
2061{
2062 ZipFile* zip = NULL;
2063 status_t result = UNKNOWN_ERROR;
2064 const char* zipFileName;
2065
2066 if (bundle->getUpdate()) {
2067 /* avoid confusion */
2068 fprintf(stderr, "ERROR: can't use '-u' with add\n");
2069 goto bail;
2070 }
2071
2072 if (bundle->getFileSpecCount() < 1) {
2073 fprintf(stderr, "ERROR: must specify zip file name\n");
2074 goto bail;
2075 }
2076 zipFileName = bundle->getFileSpecEntry(0);
2077
2078 if (bundle->getFileSpecCount() < 2) {
2079 fprintf(stderr, "NOTE: nothing to do\n");
2080 goto bail;
2081 }
2082
2083 zip = openReadWrite(zipFileName, true);
2084 if (zip == NULL) {
2085 fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
2086 goto bail;
2087 }
2088
2089 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2090 const char* fileName = bundle->getFileSpecEntry(i);
2091
2092 if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
2093 printf(" '%s'... (from gzip)\n", fileName);
2094 result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
2095 } else {
2096 if (bundle->getJunkPath()) {
2097 String8 storageName = String8(fileName).getPathLeaf();
Maurice Chu2675f762013-10-22 17:33:11 -07002098 printf(" '%s' as '%s'...\n", fileName,
2099 ResTable::normalizeForOutput(storageName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002100 result = zip->add(fileName, storageName.string(),
2101 bundle->getCompressionMethod(), NULL);
2102 } else {
2103 printf(" '%s'...\n", fileName);
2104 result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
2105 }
2106 }
2107 if (result != NO_ERROR) {
2108 fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
2109 if (result == NAME_NOT_FOUND) {
2110 fprintf(stderr, ": file not found\n");
2111 } else if (result == ALREADY_EXISTS) {
2112 fprintf(stderr, ": already exists in archive\n");
2113 } else {
2114 fprintf(stderr, "\n");
2115 }
2116 goto bail;
2117 }
2118 }
2119
2120 result = NO_ERROR;
2121
2122bail:
2123 delete zip;
2124 return (result != NO_ERROR);
2125}
2126
2127
2128/*
2129 * Delete files from an existing archive.
2130 */
2131int doRemove(Bundle* bundle)
2132{
2133 ZipFile* zip = NULL;
2134 status_t result = UNKNOWN_ERROR;
2135 const char* zipFileName;
2136
2137 if (bundle->getFileSpecCount() < 1) {
2138 fprintf(stderr, "ERROR: must specify zip file name\n");
2139 goto bail;
2140 }
2141 zipFileName = bundle->getFileSpecEntry(0);
2142
2143 if (bundle->getFileSpecCount() < 2) {
2144 fprintf(stderr, "NOTE: nothing to do\n");
2145 goto bail;
2146 }
2147
2148 zip = openReadWrite(zipFileName, false);
2149 if (zip == NULL) {
2150 fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
2151 zipFileName);
2152 goto bail;
2153 }
2154
2155 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2156 const char* fileName = bundle->getFileSpecEntry(i);
2157 ZipEntry* entry;
2158
2159 entry = zip->getEntryByName(fileName);
2160 if (entry == NULL) {
2161 printf(" '%s' NOT FOUND\n", fileName);
2162 continue;
2163 }
2164
2165 result = zip->remove(entry);
2166
2167 if (result != NO_ERROR) {
2168 fprintf(stderr, "Unable to delete '%s' from '%s'\n",
2169 bundle->getFileSpecEntry(i), zipFileName);
2170 goto bail;
2171 }
2172 }
2173
2174 /* update the archive */
2175 zip->flush();
2176
2177bail:
2178 delete zip;
2179 return (result != NO_ERROR);
2180}
2181
Adam Lesinski3921e872014-05-13 10:56:25 -07002182static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder, bool ignoreConfig=false) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002183 const size_t numDirs = dir->getDirs().size();
2184 for (size_t i = 0; i < numDirs; i++) {
Adam Lesinski3921e872014-05-13 10:56:25 -07002185 bool ignore = ignoreConfig;
2186 const sp<AaptDir>& subDir = dir->getDirs().valueAt(i);
2187 const char* dirStr = subDir->getLeaf().string();
2188 if (!ignore && strstr(dirStr, "mipmap") == dirStr) {
2189 ignore = true;
2190 }
2191 status_t err = addResourcesToBuilder(subDir, builder, ignore);
Adam Lesinskifab50872014-04-16 14:40:42 -07002192 if (err != NO_ERROR) {
2193 return err;
2194 }
2195 }
2196
2197 const size_t numFiles = dir->getFiles().size();
2198 for (size_t i = 0; i < numFiles; i++) {
2199 sp<AaptGroup> gp = dir->getFiles().valueAt(i);
2200 const size_t numConfigs = gp->getFiles().size();
2201 for (size_t j = 0; j < numConfigs; j++) {
Adam Lesinski3921e872014-05-13 10:56:25 -07002202 status_t err = NO_ERROR;
2203 if (ignoreConfig) {
2204 err = builder->getBaseSplit()->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2205 } else {
2206 err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2207 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002208 if (err != NO_ERROR) {
2209 fprintf(stderr, "Failed to add %s (%s) to builder.\n",
2210 gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string());
2211 return err;
2212 }
2213 }
2214 }
2215 return NO_ERROR;
2216}
2217
2218static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) {
2219 if (split->isBase()) {
2220 return original;
2221 }
2222
2223 String8 ext(original.getPathExtension());
2224 if (ext == String8(".apk")) {
2225 return String8::format("%s_%s%s",
2226 original.getBasePath().string(),
2227 split->getDirectorySafeName().string(),
2228 ext.string());
2229 }
2230
2231 return String8::format("%s_%s", original.string(),
2232 split->getDirectorySafeName().string());
2233}
Adam Lesinski282e1812014-01-23 18:17:42 -08002234
2235/*
2236 * Package up an asset directory and associated application files.
2237 */
2238int doPackage(Bundle* bundle)
2239{
2240 const char* outputAPKFile;
2241 int retVal = 1;
2242 status_t err;
2243 sp<AaptAssets> assets;
2244 int N;
2245 FILE* fp;
2246 String8 dependencyFile;
Adam Lesinskifab50872014-04-16 14:40:42 -07002247 sp<ApkBuilder> builder;
Adam Lesinski282e1812014-01-23 18:17:42 -08002248
Anton Krumina2ef5c02014-03-12 14:46:44 -07002249 // -c en_XA or/and ar_XB means do pseudolocalization
Adam Lesinskifab50872014-04-16 14:40:42 -07002250 sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
2251 err = configFilter->parse(bundle->getConfigurations());
Adam Lesinski282e1812014-01-23 18:17:42 -08002252 if (err != NO_ERROR) {
2253 goto bail;
2254 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002255 if (configFilter->containsPseudo()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002256 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
2257 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002258 if (configFilter->containsPseudoBidi()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002259 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
Adam Lesinski282e1812014-01-23 18:17:42 -08002260 }
2261
2262 N = bundle->getFileSpecCount();
2263 if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
Adam Lesinski09384302014-01-22 16:07:42 -08002264 && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002265 fprintf(stderr, "ERROR: no input files\n");
2266 goto bail;
2267 }
2268
2269 outputAPKFile = bundle->getOutputAPKFile();
2270
2271 // Make sure the filenames provided exist and are of the appropriate type.
2272 if (outputAPKFile) {
2273 FileType type;
2274 type = getFileType(outputAPKFile);
2275 if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
2276 fprintf(stderr,
2277 "ERROR: output file '%s' exists but is not regular file\n",
2278 outputAPKFile);
2279 goto bail;
2280 }
2281 }
2282
2283 // Load the assets.
2284 assets = new AaptAssets();
2285
2286 // Set up the resource gathering in assets if we're going to generate
2287 // dependency files. Every time we encounter a resource while slurping
2288 // the tree, we'll add it to these stores so we have full resource paths
2289 // to write to a dependency file.
2290 if (bundle->getGenDependencies()) {
2291 sp<FilePathStore> resPathStore = new FilePathStore;
2292 assets->setFullResPaths(resPathStore);
2293 sp<FilePathStore> assetPathStore = new FilePathStore;
2294 assets->setFullAssetPaths(assetPathStore);
2295 }
2296
2297 err = assets->slurpFromArgs(bundle);
2298 if (err < 0) {
2299 goto bail;
2300 }
2301
2302 if (bundle->getVerbose()) {
2303 assets->print(String8());
2304 }
2305
Adam Lesinskifab50872014-04-16 14:40:42 -07002306 // Create the ApkBuilder, which will collect the compiled files
2307 // to write to the final APK (or sets of APKs if we are building
2308 // a Split APK.
2309 builder = new ApkBuilder(configFilter);
2310
2311 // If we are generating a Split APK, find out which configurations to split on.
2312 if (bundle->getSplitConfigurations().size() > 0) {
2313 const Vector<String8>& splitStrs = bundle->getSplitConfigurations();
2314 const size_t numSplits = splitStrs.size();
2315 for (size_t i = 0; i < numSplits; i++) {
2316 std::set<ConfigDescription> configs;
2317 if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {
2318 fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());
2319 goto bail;
2320 }
2321
2322 err = builder->createSplitForConfigs(configs);
2323 if (err != NO_ERROR) {
2324 goto bail;
2325 }
2326 }
2327 }
2328
Adam Lesinski282e1812014-01-23 18:17:42 -08002329 // If they asked for any fileAs that need to be compiled, do so.
2330 if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002331 err = buildResources(bundle, assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002332 if (err != 0) {
2333 goto bail;
2334 }
2335 }
2336
2337 // At this point we've read everything and processed everything. From here
2338 // on out it's just writing output files.
2339 if (SourcePos::hasErrors()) {
2340 goto bail;
2341 }
2342
2343 // Update symbols with information about which ones are needed as Java symbols.
2344 assets->applyJavaSymbols();
2345 if (SourcePos::hasErrors()) {
2346 goto bail;
2347 }
2348
2349 // If we've been asked to generate a dependency file, do that here
2350 if (bundle->getGenDependencies()) {
2351 // If this is the packaging step, generate the dependency file next to
2352 // the output apk (e.g. bin/resources.ap_.d)
2353 if (outputAPKFile) {
2354 dependencyFile = String8(outputAPKFile);
2355 // Add the .d extension to the dependency file.
2356 dependencyFile.append(".d");
2357 } else {
2358 // Else if this is the R.java dependency generation step,
2359 // generate the dependency file in the R.java package subdirectory
2360 // e.g. gen/com/foo/app/R.java.d
2361 dependencyFile = String8(bundle->getRClassDir());
2362 dependencyFile.appendPath("R.java.d");
2363 }
2364 // Make sure we have a clean dependency file to start with
2365 fp = fopen(dependencyFile, "w");
2366 fclose(fp);
2367 }
2368
2369 // Write out R.java constants
2370 if (!assets->havePrivateSymbols()) {
2371 if (bundle->getCustomPackage() == NULL) {
2372 // Write the R.java file into the appropriate class directory
2373 // e.g. gen/com/foo/app/R.java
2374 err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
2375 } else {
2376 const String8 customPkg(bundle->getCustomPackage());
2377 err = writeResourceSymbols(bundle, assets, customPkg, true);
2378 }
2379 if (err < 0) {
2380 goto bail;
2381 }
2382 // If we have library files, we're going to write our R.java file into
2383 // the appropriate class directory for those libraries as well.
2384 // e.g. gen/com/foo/app/lib/R.java
2385 if (bundle->getExtraPackages() != NULL) {
2386 // Split on colon
2387 String8 libs(bundle->getExtraPackages());
2388 char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
2389 while (packageString != NULL) {
2390 // Write the R.java file out with the correct package name
2391 err = writeResourceSymbols(bundle, assets, String8(packageString), true);
2392 if (err < 0) {
2393 goto bail;
2394 }
2395 packageString = strtok(NULL, ":");
2396 }
2397 libs.unlockBuffer();
2398 }
2399 } else {
2400 err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
2401 if (err < 0) {
2402 goto bail;
2403 }
2404 err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
2405 if (err < 0) {
2406 goto bail;
2407 }
2408 }
2409
2410 // Write out the ProGuard file
2411 err = writeProguardFile(bundle, assets);
2412 if (err < 0) {
2413 goto bail;
2414 }
2415
2416 // Write the apk
2417 if (outputAPKFile) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002418 // Gather all resources and add them to the APK Builder. The builder will then
2419 // figure out which Split they belong in.
2420 err = addResourcesToBuilder(assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002421 if (err != NO_ERROR) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002422 goto bail;
2423 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002424
2425 const Vector<sp<ApkSplit> >& splits = builder->getSplits();
2426 const size_t numSplits = splits.size();
2427 for (size_t i = 0; i < numSplits; i++) {
2428 const sp<ApkSplit>& split = splits[i];
2429 String8 outputPath = buildApkName(String8(outputAPKFile), split);
2430 err = writeAPK(bundle, outputPath, split);
2431 if (err != NO_ERROR) {
2432 fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
2433 goto bail;
2434 }
2435 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002436 }
2437
2438 // If we've been asked to generate a dependency file, we need to finish up here.
2439 // the writeResourceSymbols and writeAPK functions have already written the target
2440 // half of the dependency file, now we need to write the prerequisites. (files that
2441 // the R.java file or .ap_ file depend on)
2442 if (bundle->getGenDependencies()) {
2443 // Now that writeResourceSymbols or writeAPK has taken care of writing
2444 // the targets to our dependency file, we'll write the prereqs
2445 fp = fopen(dependencyFile, "a+");
2446 fprintf(fp, " : ");
2447 bool includeRaw = (outputAPKFile != NULL);
2448 err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
2449 // Also manually add the AndroidManifeset since it's not under res/ or assets/
2450 // and therefore was not added to our pathstores during slurping
2451 fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
2452 fclose(fp);
2453 }
2454
2455 retVal = 0;
2456bail:
2457 if (SourcePos::hasErrors()) {
2458 SourcePos::printErrors(stderr);
2459 }
2460 return retVal;
2461}
2462
2463/*
2464 * Do PNG Crunching
2465 * PRECONDITIONS
2466 * -S flag points to a source directory containing drawable* folders
2467 * -C flag points to destination directory. The folder structure in the
2468 * source directory will be mirrored to the destination (cache) directory
2469 *
2470 * POSTCONDITIONS
2471 * Destination directory will be updated to match the PNG files in
Maurice Chu2675f762013-10-22 17:33:11 -07002472 * the source directory.
Adam Lesinski282e1812014-01-23 18:17:42 -08002473 */
2474int doCrunch(Bundle* bundle)
2475{
2476 fprintf(stdout, "Crunching PNG Files in ");
2477 fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
2478 fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
2479
2480 updatePreProcessedCache(bundle);
2481
2482 return NO_ERROR;
2483}
2484
2485/*
2486 * Do PNG Crunching on a single flag
2487 * -i points to a single png file
2488 * -o points to a single png output file
2489 */
2490int doSingleCrunch(Bundle* bundle)
2491{
2492 fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile());
2493 fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile());
2494
2495 String8 input(bundle->getSingleCrunchInputFile());
2496 String8 output(bundle->getSingleCrunchOutputFile());
2497
2498 if (preProcessImageToCache(bundle, input, output) != NO_ERROR) {
2499 // we can't return the status_t as it gets truncate to the lower 8 bits.
2500 return 42;
2501 }
2502
2503 return NO_ERROR;
2504}
2505
2506char CONSOLE_DATA[2925] = {
2507 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2508 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2509 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2510 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2511 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63,
2512 86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83,
2513 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2514 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2515 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81,
2516 81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32,
2517 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2518 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2519 32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59,
2520 59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2521 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2522 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59,
2523 59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32,
2524 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2525 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2526 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87,
2527 58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32,
2528 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2529 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2530 47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58,
2531 121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2532 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2533 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81,
2534 81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32,
2535 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2536 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2537 32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59,
2538 59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2539 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2540 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59,
2541 59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32,
2542 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2543 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2544 32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81,
2545 70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32,
2546 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2547 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2548 32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81,
2549 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2550 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2551 32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109,
2552 81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32,
2553 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2554 32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59,
2555 59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59,
2556 60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2557 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59,
2558 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46,
2559 61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32,
2560 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2561 32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2562 59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81,
2563 109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32,
2564 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81,
2565 67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2566 59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58,
2567 61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32,
2568 32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59,
2569 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37,
2570 73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2571 59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2572 32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59,
2573 59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96,
2574 46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61,
2575 97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32,
2576 46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61,
2577 119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59,
2578 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32,
2579 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2580 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119,
2581 119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2582 59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10,
2583 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2584 32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81,
2585 81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41,
2586 87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2587 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81,
2588 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58,
2589 45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32,
2590 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2591 32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2592 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32,
2593 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2594 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58,
2595 59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58,
2596 59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2597 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2598 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81,
2599 81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32,
2600 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2601 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2602 32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2603 81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2604 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2605 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106,
2606 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59,
2607 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2608 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2609 32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2610 81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32,
2611 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2612 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2613 58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59,
2614 61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2615 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2616 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81,
2617 81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32,
2618 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2619 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2620 32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2621 81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2622 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2623 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59,
2624 59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59,
2625 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2626 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2627 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33,
2628 58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32,
2629 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2630 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2631 61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59,
2632 59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32,
2633 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32,
2634 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95,
2635 32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59,
2636 61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2637 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2638 32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45,
2639 59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32,
2640 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32,
2641 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59,
2642 59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59,
2643 59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2644 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2645 32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2646 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2647 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2648 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2649 46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61,
2650 46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2651 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2652 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59,
2653 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59,
2654 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2655 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2656 32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32,
2657 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32,
2658 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2659 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61,
2660 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59,
2661 59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2662 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2663 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32,
2664 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32,
2665 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2666 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2667 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2668 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2669 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10
2670 };