blob: ac1ae70b2309d9e8eac8375bea3508378b9ef112 [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 "Bundle.h"
Adam Lesinski2c72b682014-06-24 09:56:01 -07008#include "Images.h"
9#include "Main.h"
Adam Lesinski282e1812014-01-23 18:17:42 -080010#include "ResourceFilter.h"
11#include "ResourceTable.h"
Adam Lesinski282e1812014-01-23 18:17:42 -080012#include "XMLNode.h"
13
Adam Lesinski282e1812014-01-23 18:17:42 -080014#include <utils/Errors.h>
Adam Lesinski2c72b682014-06-24 09:56:01 -070015#include <utils/KeyedVector.h>
16#include <utils/List.h>
17#include <utils/Log.h>
18#include <utils/SortedVector.h>
19#include <utils/threads.h>
20#include <utils/Vector.h>
Adam Lesinski282e1812014-01-23 18:17:42 -080021
Adam Lesinski282e1812014-01-23 18:17:42 -080022#include <errno.h>
Adam Lesinski2c72b682014-06-24 09:56:01 -070023#include <fcntl.h>
Adam Lesinski282e1812014-01-23 18:17:42 -080024
25using namespace android;
26
27/*
28 * Show version info. All the cool kids do it.
29 */
30int doVersion(Bundle* bundle)
31{
32 if (bundle->getFileSpecCount() != 0) {
33 printf("(ignoring extra arguments)\n");
34 }
35 printf("Android Asset Packaging Tool, v0.2\n");
36
37 return 0;
38}
39
40
41/*
42 * Open the file read only. The call fails if the file doesn't exist.
43 *
44 * Returns NULL on failure.
45 */
46ZipFile* openReadOnly(const char* fileName)
47{
48 ZipFile* zip;
49 status_t result;
50
51 zip = new ZipFile;
52 result = zip->open(fileName, ZipFile::kOpenReadOnly);
53 if (result != NO_ERROR) {
54 if (result == NAME_NOT_FOUND) {
55 fprintf(stderr, "ERROR: '%s' not found\n", fileName);
56 } else if (result == PERMISSION_DENIED) {
57 fprintf(stderr, "ERROR: '%s' access denied\n", fileName);
58 } else {
59 fprintf(stderr, "ERROR: failed opening '%s' as Zip file\n",
60 fileName);
61 }
62 delete zip;
63 return NULL;
64 }
65
66 return zip;
67}
68
69/*
70 * Open the file read-write. The file will be created if it doesn't
71 * already exist and "okayToCreate" is set.
72 *
73 * Returns NULL on failure.
74 */
75ZipFile* openReadWrite(const char* fileName, bool okayToCreate)
76{
77 ZipFile* zip = NULL;
78 status_t result;
79 int flags;
80
81 flags = ZipFile::kOpenReadWrite;
82 if (okayToCreate) {
83 flags |= ZipFile::kOpenCreate;
84 }
85
86 zip = new ZipFile;
87 result = zip->open(fileName, flags);
88 if (result != NO_ERROR) {
89 delete zip;
90 zip = NULL;
91 goto bail;
92 }
93
94bail:
95 return zip;
96}
97
98
99/*
100 * Return a short string describing the compression method.
101 */
102const char* compressionName(int method)
103{
104 if (method == ZipEntry::kCompressStored) {
105 return "Stored";
106 } else if (method == ZipEntry::kCompressDeflated) {
107 return "Deflated";
108 } else {
109 return "Unknown";
110 }
111}
112
113/*
114 * Return the percent reduction in size (0% == no compression).
115 */
116int calcPercent(long uncompressedLen, long compressedLen)
117{
118 if (!uncompressedLen) {
119 return 0;
120 } else {
121 return (int) (100.0 - (compressedLen * 100.0) / uncompressedLen + 0.5);
122 }
123}
124
125/*
126 * Handle the "list" command, which can be a simple file dump or
127 * a verbose listing.
128 *
129 * The verbose listing closely matches the output of the Info-ZIP "unzip"
130 * command.
131 */
132int doList(Bundle* bundle)
133{
134 int result = 1;
135 ZipFile* zip = NULL;
136 const ZipEntry* entry;
137 long totalUncLen, totalCompLen;
138 const char* zipFileName;
139
140 if (bundle->getFileSpecCount() != 1) {
141 fprintf(stderr, "ERROR: specify zip file name (only)\n");
142 goto bail;
143 }
144 zipFileName = bundle->getFileSpecEntry(0);
145
146 zip = openReadOnly(zipFileName);
147 if (zip == NULL) {
148 goto bail;
149 }
150
151 int count, i;
152
153 if (bundle->getVerbose()) {
154 printf("Archive: %s\n", zipFileName);
155 printf(
156 " Length Method Size Ratio Offset Date Time CRC-32 Name\n");
157 printf(
158 "-------- ------ ------- ----- ------- ---- ---- ------ ----\n");
159 }
160
161 totalUncLen = totalCompLen = 0;
162
163 count = zip->getNumEntries();
164 for (i = 0; i < count; i++) {
165 entry = zip->getEntryByIndex(i);
166 if (bundle->getVerbose()) {
167 char dateBuf[32];
168 time_t when;
169
170 when = entry->getModWhen();
171 strftime(dateBuf, sizeof(dateBuf), "%m-%d-%y %H:%M",
172 localtime(&when));
173
174 printf("%8ld %-7.7s %7ld %3d%% %8zd %s %08lx %s\n",
175 (long) entry->getUncompressedLen(),
176 compressionName(entry->getCompressionMethod()),
177 (long) entry->getCompressedLen(),
178 calcPercent(entry->getUncompressedLen(),
179 entry->getCompressedLen()),
180 (size_t) entry->getLFHOffset(),
181 dateBuf,
182 entry->getCRC32(),
183 entry->getFileName());
184 } else {
185 printf("%s\n", entry->getFileName());
186 }
187
188 totalUncLen += entry->getUncompressedLen();
189 totalCompLen += entry->getCompressedLen();
190 }
191
192 if (bundle->getVerbose()) {
193 printf(
194 "-------- ------- --- -------\n");
195 printf("%8ld %7ld %2d%% %d files\n",
196 totalUncLen,
197 totalCompLen,
198 calcPercent(totalUncLen, totalCompLen),
199 zip->getNumEntries());
200 }
201
202 if (bundle->getAndroidList()) {
203 AssetManager assets;
204 if (!assets.addAssetPath(String8(zipFileName), NULL)) {
205 fprintf(stderr, "ERROR: list -a failed because assets could not be loaded\n");
206 goto bail;
207 }
208
209 const ResTable& res = assets.getResources(false);
210 if (&res == NULL) {
211 printf("\nNo resource table found.\n");
212 } else {
213#ifndef HAVE_ANDROID_OS
214 printf("\nResource table:\n");
215 res.print(false);
216#endif
217 }
218
219 Asset* manifestAsset = assets.openNonAsset("AndroidManifest.xml",
220 Asset::ACCESS_BUFFER);
221 if (manifestAsset == NULL) {
222 printf("\nNo AndroidManifest.xml found.\n");
223 } else {
224 printf("\nAndroid manifest:\n");
225 ResXMLTree tree;
226 tree.setTo(manifestAsset->getBuffer(true),
227 manifestAsset->getLength());
228 printXMLBlock(&tree);
229 }
230 delete manifestAsset;
231 }
232
233 result = 0;
234
235bail:
236 delete zip;
237 return result;
238}
239
240static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes)
241{
242 size_t N = tree.getAttributeCount();
243 for (size_t i=0; i<N; i++) {
244 if (tree.getAttributeNameResID(i) == attrRes) {
245 return (ssize_t)i;
246 }
247 }
248 return -1;
249}
250
251String8 getAttribute(const ResXMLTree& tree, const char* ns,
252 const char* attr, String8* outError)
253{
254 ssize_t idx = tree.indexOfAttribute(ns, attr);
255 if (idx < 0) {
256 return String8();
257 }
258 Res_value value;
259 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
260 if (value.dataType != Res_value::TYPE_STRING) {
261 if (outError != NULL) {
262 *outError = "attribute is not a string value";
263 }
264 return String8();
265 }
266 }
267 size_t len;
268 const uint16_t* str = tree.getAttributeStringValue(idx, &len);
269 return str ? String8(str, len) : String8();
270}
271
272static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError)
273{
274 ssize_t idx = indexOfAttribute(tree, attrRes);
275 if (idx < 0) {
276 return String8();
277 }
278 Res_value value;
279 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
280 if (value.dataType != Res_value::TYPE_STRING) {
281 if (outError != NULL) {
282 *outError = "attribute is not a string value";
283 }
284 return String8();
285 }
286 }
287 size_t len;
288 const uint16_t* str = tree.getAttributeStringValue(idx, &len);
289 return str ? String8(str, len) : String8();
290}
291
292static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes,
293 String8* outError, int32_t defValue = -1)
294{
295 ssize_t idx = indexOfAttribute(tree, attrRes);
296 if (idx < 0) {
297 return defValue;
298 }
299 Res_value value;
300 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
301 if (value.dataType < Res_value::TYPE_FIRST_INT
302 || value.dataType > Res_value::TYPE_LAST_INT) {
303 if (outError != NULL) {
304 *outError = "attribute is not an integer value";
305 }
306 return defValue;
307 }
308 }
309 return value.data;
310}
311
312static int32_t getResolvedIntegerAttribute(const ResTable* resTable, const ResXMLTree& tree,
313 uint32_t attrRes, String8* outError, int32_t defValue = -1)
314{
315 ssize_t idx = indexOfAttribute(tree, attrRes);
316 if (idx < 0) {
317 return defValue;
318 }
319 Res_value value;
320 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
321 if (value.dataType == Res_value::TYPE_REFERENCE) {
322 resTable->resolveReference(&value, 0);
323 }
324 if (value.dataType < Res_value::TYPE_FIRST_INT
325 || value.dataType > Res_value::TYPE_LAST_INT) {
326 if (outError != NULL) {
327 *outError = "attribute is not an integer value";
328 }
329 return defValue;
330 }
331 }
332 return value.data;
333}
334
335static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree,
336 uint32_t attrRes, String8* outError)
337{
338 ssize_t idx = indexOfAttribute(tree, attrRes);
339 if (idx < 0) {
340 return String8();
341 }
342 Res_value value;
343 if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
344 if (value.dataType == Res_value::TYPE_STRING) {
345 size_t len;
346 const uint16_t* str = tree.getAttributeStringValue(idx, &len);
347 return str ? String8(str, len) : String8();
348 }
349 resTable->resolveReference(&value, 0);
350 if (value.dataType != Res_value::TYPE_STRING) {
351 if (outError != NULL) {
352 *outError = "attribute is not a string value";
353 }
354 return String8();
355 }
356 }
357 size_t len;
358 const Res_value* value2 = &value;
359 const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len);
360 return str ? String8(str, len) : String8();
361}
362
363static void getResolvedResourceAttribute(Res_value* value, const ResTable* resTable,
364 const ResXMLTree& tree, uint32_t attrRes, String8* outError)
365{
366 ssize_t idx = indexOfAttribute(tree, attrRes);
367 if (idx < 0) {
368 if (outError != NULL) {
369 *outError = "attribute could not be found";
370 }
371 return;
372 }
373 if (tree.getAttributeValue(idx, value) != NO_ERROR) {
374 if (value->dataType == Res_value::TYPE_REFERENCE) {
375 resTable->resolveReference(value, 0);
376 }
377 // The attribute was found and was resolved if need be.
378 return;
379 }
380 if (outError != NULL) {
381 *outError = "error getting resolved resource attribute";
382 }
383}
384
Maurice Chu76327312013-10-16 18:28:46 -0700385static void printResolvedResourceAttribute(const ResTable* resTable, const ResXMLTree& tree,
386 uint32_t attrRes, String8 attrLabel, String8* outError)
387{
388 Res_value value;
389 getResolvedResourceAttribute(&value, resTable, tree, attrRes, outError);
390 if (*outError != "") {
391 *outError = "error print resolved resource attribute";
392 return;
393 }
394 if (value.dataType == Res_value::TYPE_STRING) {
395 String8 result = getResolvedAttribute(resTable, tree, attrRes, outError);
Maurice Chu2675f762013-10-22 17:33:11 -0700396 printf("%s='%s'", attrLabel.string(),
397 ResTable::normalizeForOutput(result.string()).string());
Maurice Chu76327312013-10-16 18:28:46 -0700398 } else if (Res_value::TYPE_FIRST_INT <= value.dataType &&
399 value.dataType <= Res_value::TYPE_LAST_INT) {
400 printf("%s='%d'", attrLabel.string(), value.data);
401 } else {
402 printf("%s='0x%x'", attrLabel.string(), (int)value.data);
403 }
404}
405
Adam Lesinski282e1812014-01-23 18:17:42 -0800406// These are attribute resource constants for the platform, as found
407// in android.R.attr
408enum {
409 LABEL_ATTR = 0x01010001,
410 ICON_ATTR = 0x01010002,
411 NAME_ATTR = 0x01010003,
Adam Lesinskia5018c92013-09-30 16:23:15 -0700412 PERMISSION_ATTR = 0x01010006,
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700413 EXPORTED_ATTR = 0x01010010,
414 GRANT_URI_PERMISSIONS_ATTR = 0x0101001b,
Adam Lesinski94fc9122013-09-30 17:16:09 -0700415 RESOURCE_ATTR = 0x01010025,
Adam Lesinski282e1812014-01-23 18:17:42 -0800416 DEBUGGABLE_ATTR = 0x0101000f,
417 VALUE_ATTR = 0x01010024,
418 VERSION_CODE_ATTR = 0x0101021b,
419 VERSION_NAME_ATTR = 0x0101021c,
420 SCREEN_ORIENTATION_ATTR = 0x0101001e,
421 MIN_SDK_VERSION_ATTR = 0x0101020c,
422 MAX_SDK_VERSION_ATTR = 0x01010271,
423 REQ_TOUCH_SCREEN_ATTR = 0x01010227,
424 REQ_KEYBOARD_TYPE_ATTR = 0x01010228,
425 REQ_HARD_KEYBOARD_ATTR = 0x01010229,
426 REQ_NAVIGATION_ATTR = 0x0101022a,
427 REQ_FIVE_WAY_NAV_ATTR = 0x01010232,
428 TARGET_SDK_VERSION_ATTR = 0x01010270,
429 TEST_ONLY_ATTR = 0x01010272,
430 ANY_DENSITY_ATTR = 0x0101026c,
431 GL_ES_VERSION_ATTR = 0x01010281,
432 SMALL_SCREEN_ATTR = 0x01010284,
433 NORMAL_SCREEN_ATTR = 0x01010285,
434 LARGE_SCREEN_ATTR = 0x01010286,
435 XLARGE_SCREEN_ATTR = 0x010102bf,
436 REQUIRED_ATTR = 0x0101028e,
437 SCREEN_SIZE_ATTR = 0x010102ca,
438 SCREEN_DENSITY_ATTR = 0x010102cb,
439 REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364,
440 COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365,
441 LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366,
442 PUBLIC_KEY_ATTR = 0x010103a6,
Adam Lesinski94fc9122013-09-30 17:16:09 -0700443 CATEGORY_ATTR = 0x010103e8,
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800444 BANNER_ATTR = 0x10103f2,
Adam Lesinski282e1812014-01-23 18:17:42 -0800445};
446
Maurice Chu2675f762013-10-22 17:33:11 -0700447String8 getComponentName(String8 &pkgName, String8 &componentName) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800448 ssize_t idx = componentName.find(".");
449 String8 retStr(pkgName);
450 if (idx == 0) {
451 retStr += componentName;
452 } else if (idx < 0) {
453 retStr += ".";
454 retStr += componentName;
455 } else {
Maurice Chu2675f762013-10-22 17:33:11 -0700456 return componentName;
Adam Lesinski282e1812014-01-23 18:17:42 -0800457 }
Maurice Chu2675f762013-10-22 17:33:11 -0700458 return retStr;
Adam Lesinski282e1812014-01-23 18:17:42 -0800459}
460
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700461static void printCompatibleScreens(ResXMLTree& tree, String8* outError) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800462 size_t len;
463 ResXMLTree::event_code_t code;
464 int depth = 0;
465 bool first = true;
466 printf("compatible-screens:");
467 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
468 if (code == ResXMLTree::END_TAG) {
469 depth--;
470 if (depth < 0) {
471 break;
472 }
473 continue;
474 }
475 if (code != ResXMLTree::START_TAG) {
476 continue;
477 }
478 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700479 const char16_t* ctag16 = tree.getElementName(&len);
480 if (ctag16 == NULL) {
481 *outError = "failed to get XML element name (bad string pool)";
482 return;
483 }
484 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -0800485 if (tag == "screen") {
486 int32_t screenSize = getIntegerAttribute(tree,
487 SCREEN_SIZE_ATTR, NULL, -1);
488 int32_t screenDensity = getIntegerAttribute(tree,
489 SCREEN_DENSITY_ATTR, NULL, -1);
490 if (screenSize > 0 && screenDensity > 0) {
491 if (!first) {
492 printf(",");
493 }
494 first = false;
495 printf("'%d/%d'", screenSize, screenDensity);
496 }
497 }
498 }
499 printf("\n");
500}
501
Adam Lesinski58f1f362013-11-12 12:59:08 -0800502static void printUsesPermission(const String8& name, bool optional=false, int maxSdkVersion=-1) {
503 printf("uses-permission: name='%s'", ResTable::normalizeForOutput(name.string()).string());
504 if (maxSdkVersion != -1) {
505 printf(" maxSdkVersion='%d'", maxSdkVersion);
506 }
507 printf("\n");
508
509 if (optional) {
510 printf("optional-permission: name='%s'",
511 ResTable::normalizeForOutput(name.string()).string());
512 if (maxSdkVersion != -1) {
513 printf(" maxSdkVersion='%d'", maxSdkVersion);
514 }
515 printf("\n");
516 }
517}
518
519static void printUsesImpliedPermission(const String8& name, const String8& reason) {
520 printf("uses-implied-permission: name='%s' reason='%s'\n",
521 ResTable::normalizeForOutput(name.string()).string(),
522 ResTable::normalizeForOutput(reason.string()).string());
523}
524
Adam Lesinski94fc9122013-09-30 17:16:09 -0700525Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool offHost,
526 String8 *outError = NULL)
527{
528 Asset* aidAsset = assets.openNonAsset(xmlPath, Asset::ACCESS_BUFFER);
529 if (aidAsset == NULL) {
530 if (outError != NULL) *outError = "xml resource does not exist";
531 return Vector<String8>();
532 }
533
534 const String8 serviceTagName(offHost ? "offhost-apdu-service" : "host-apdu-service");
535
536 bool withinApduService = false;
537 Vector<String8> categories;
538
539 String8 error;
540 ResXMLTree tree;
541 tree.setTo(aidAsset->getBuffer(true), aidAsset->getLength());
542
543 size_t len;
544 int depth = 0;
545 ResXMLTree::event_code_t code;
546 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
547 if (code == ResXMLTree::END_TAG) {
548 depth--;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700549 const char16_t* ctag16 = tree.getElementName(&len);
550 if (ctag16 == NULL) {
551 *outError = "failed to get XML element name (bad string pool)";
552 return Vector<String8>();
553 }
554 String8 tag(ctag16);
Adam Lesinski94fc9122013-09-30 17:16:09 -0700555
556 if (depth == 0 && tag == serviceTagName) {
557 withinApduService = false;
558 }
559
560 } else if (code == ResXMLTree::START_TAG) {
561 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700562 const char16_t* ctag16 = tree.getElementName(&len);
563 if (ctag16 == NULL) {
564 *outError = "failed to get XML element name (bad string pool)";
565 return Vector<String8>();
566 }
567 String8 tag(ctag16);
Adam Lesinski94fc9122013-09-30 17:16:09 -0700568
569 if (depth == 1) {
570 if (tag == serviceTagName) {
571 withinApduService = true;
572 }
573 } else if (depth == 2 && withinApduService) {
574 if (tag == "aid-group") {
575 String8 category = getAttribute(tree, CATEGORY_ATTR, &error);
576 if (error != "") {
577 if (outError != NULL) *outError = error;
578 return Vector<String8>();
579 }
580
581 categories.add(category);
582 }
583 }
584 }
585 }
586 aidAsset->close();
587 return categories;
588}
589
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700590static void printComponentPresence(const char* componentName) {
591 printf("provides-component:'%s'\n", componentName);
592}
593
Adam Lesinski2c72b682014-06-24 09:56:01 -0700594/**
595 * Represents a feature that has been automatically added due to
596 * a pre-requisite or some other reason.
597 */
598struct ImpliedFeature {
599 /**
600 * Name of the implied feature.
601 */
602 String8 name;
603
604 /**
605 * List of human-readable reasons for why this feature was implied.
606 */
607 SortedVector<String8> reasons;
608};
609
610/**
611 * Represents a <feature-group> tag in the AndroidManifest.xml
612 */
613struct FeatureGroup {
614 /**
615 * Human readable label
616 */
617 String8 label;
618
619 /**
620 * Explicit features defined in the group
621 */
622 KeyedVector<String8, bool> features;
623};
624
625static void addImpliedFeature(KeyedVector<String8, ImpliedFeature>* impliedFeatures,
626 const char* name, const char* reason) {
627 String8 name8(name);
628 ssize_t idx = impliedFeatures->indexOfKey(name8);
629 if (idx < 0) {
630 idx = impliedFeatures->add(name8, ImpliedFeature());
631 impliedFeatures->editValueAt(idx).name = name8;
632 }
633 impliedFeatures->editValueAt(idx).reasons.add(String8(reason));
634}
635
636static void printFeatureGroup(const FeatureGroup& grp,
637 const KeyedVector<String8, ImpliedFeature>* impliedFeatures = NULL) {
638 printf("feature-group: label='%s'\n", grp.label.string());
639
640 const size_t numFeatures = grp.features.size();
641 for (size_t i = 0; i < numFeatures; i++) {
642 if (!grp.features[i]) {
643 continue;
644 }
645
646 const String8& featureName = grp.features.keyAt(i);
647 printf(" uses-feature: name='%s'\n",
648 ResTable::normalizeForOutput(featureName.string()).string());
649 }
650
651 const size_t numImpliedFeatures =
652 (impliedFeatures != NULL) ? impliedFeatures->size() : 0;
653 for (size_t i = 0; i < numImpliedFeatures; i++) {
654 const ImpliedFeature& impliedFeature = impliedFeatures->valueAt(i);
655 if (grp.features.indexOfKey(impliedFeature.name) >= 0) {
656 // The feature is explicitly set, no need to use implied
657 // definition.
658 continue;
659 }
660
661 String8 printableFeatureName(ResTable::normalizeForOutput(
662 impliedFeature.name.string()));
663 printf(" uses-feature: name='%s'\n", printableFeatureName.string());
664 printf(" uses-implied-feature: name='%s' reason='",
665 printableFeatureName.string());
666 const size_t numReasons = impliedFeature.reasons.size();
667 for (size_t j = 0; j < numReasons; j++) {
668 printf("%s", impliedFeature.reasons[j].string());
669 if (j + 2 < numReasons) {
670 printf(", ");
671 } else if (j + 1 < numReasons) {
672 printf(", and ");
673 }
674 }
675 printf("'\n");
676 }
677}
678
679static void addParentFeatures(FeatureGroup* grp, const String8& name) {
680 if (name == "android.hardware.camera.autofocus" ||
681 name == "android.hardware.camera.flash") {
682 grp->features.add(String8("android.hardware.camera"), true);
683 } else if (name == "android.hardware.location.gps" ||
684 name == "android.hardware.location.network") {
685 grp->features.add(String8("android.hardware.location"), true);
686 } else if (name == "android.hardware.touchscreen.multitouch") {
687 grp->features.add(String8("android.hardware.touchscreen"), true);
688 } else if (name == "android.hardware.touchscreen.multitouch.distinct") {
689 grp->features.add(String8("android.hardware.touchscreen.multitouch"), true);
690 grp->features.add(String8("android.hardware.touchscreen"), true);
691 }
692}
693
Adam Lesinski282e1812014-01-23 18:17:42 -0800694/*
695 * Handle the "dump" command, to extract select data from an archive.
696 */
697extern char CONSOLE_DATA[2925]; // see EOF
698int doDump(Bundle* bundle)
699{
700 status_t result = UNKNOWN_ERROR;
701 Asset* asset = NULL;
702
703 if (bundle->getFileSpecCount() < 1) {
704 fprintf(stderr, "ERROR: no dump option specified\n");
705 return 1;
706 }
707
708 if (bundle->getFileSpecCount() < 2) {
709 fprintf(stderr, "ERROR: no dump file specified\n");
710 return 1;
711 }
712
713 const char* option = bundle->getFileSpecEntry(0);
714 const char* filename = bundle->getFileSpecEntry(1);
715
716 AssetManager assets;
Narayan Kamathf85e41f2014-01-24 14:14:30 +0000717 int32_t assetsCookie;
Adam Lesinski282e1812014-01-23 18:17:42 -0800718 if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
719 fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
720 return 1;
721 }
722
723 // Make a dummy config for retrieving resources... we need to supply
724 // non-default values for some configs so that we can retrieve resources
725 // in the app that don't have a default. The most important of these is
726 // the API version because key resources like icons will have an implicit
727 // version if they are using newer config types like density.
728 ResTable_config config;
Narayan Kamath91447d82014-01-21 15:32:36 +0000729 memset(&config, 0, sizeof(ResTable_config));
Adam Lesinski282e1812014-01-23 18:17:42 -0800730 config.language[0] = 'e';
731 config.language[1] = 'n';
732 config.country[0] = 'U';
733 config.country[1] = 'S';
734 config.orientation = ResTable_config::ORIENTATION_PORT;
735 config.density = ResTable_config::DENSITY_MEDIUM;
736 config.sdkVersion = 10000; // Very high.
737 config.screenWidthDp = 320;
738 config.screenHeightDp = 480;
739 config.smallestScreenWidthDp = 320;
740 assets.setConfiguration(config);
741
742 const ResTable& res = assets.getResources(false);
743 if (&res == NULL) {
744 fprintf(stderr, "ERROR: dump failed because no resource table was found\n");
745 goto bail;
Adam Lesinski25e9d552014-05-19 15:01:43 -0700746 } else if (res.getError() != NO_ERROR) {
747 fprintf(stderr, "ERROR: dump failed because the resource table is invalid/corrupt.\n");
748 goto bail;
Adam Lesinski282e1812014-01-23 18:17:42 -0800749 }
750
751 if (strcmp("resources", option) == 0) {
752#ifndef HAVE_ANDROID_OS
753 res.print(bundle->getValues());
754#endif
755
756 } else if (strcmp("strings", option) == 0) {
757 const ResStringPool* pool = res.getTableStringBlock(0);
758 printStringPool(pool);
759
760 } else if (strcmp("xmltree", option) == 0) {
761 if (bundle->getFileSpecCount() < 3) {
762 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
763 goto bail;
764 }
765
766 for (int i=2; i<bundle->getFileSpecCount(); i++) {
767 const char* resname = bundle->getFileSpecEntry(i);
768 ResXMLTree tree;
769 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
770 if (asset == NULL) {
771 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
772 goto bail;
773 }
774
775 if (tree.setTo(asset->getBuffer(true),
776 asset->getLength()) != NO_ERROR) {
777 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
778 goto bail;
779 }
780 tree.restart();
781 printXMLBlock(&tree);
782 tree.uninit();
783 delete asset;
784 asset = NULL;
785 }
786
787 } else if (strcmp("xmlstrings", option) == 0) {
788 if (bundle->getFileSpecCount() < 3) {
789 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
790 goto bail;
791 }
792
793 for (int i=2; i<bundle->getFileSpecCount(); i++) {
794 const char* resname = bundle->getFileSpecEntry(i);
795 ResXMLTree tree;
796 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
797 if (asset == NULL) {
798 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
799 goto bail;
800 }
801
802 if (tree.setTo(asset->getBuffer(true),
803 asset->getLength()) != NO_ERROR) {
804 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
805 goto bail;
806 }
807 printStringPool(&tree.getStrings());
808 delete asset;
809 asset = NULL;
810 }
811
812 } else {
813 ResXMLTree tree;
814 asset = assets.openNonAsset("AndroidManifest.xml",
815 Asset::ACCESS_BUFFER);
816 if (asset == NULL) {
817 fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
818 goto bail;
819 }
820
821 if (tree.setTo(asset->getBuffer(true),
822 asset->getLength()) != NO_ERROR) {
823 fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
824 goto bail;
825 }
826 tree.restart();
827
828 if (strcmp("permissions", option) == 0) {
829 size_t len;
830 ResXMLTree::event_code_t code;
831 int depth = 0;
832 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
833 if (code == ResXMLTree::END_TAG) {
834 depth--;
835 continue;
836 }
837 if (code != ResXMLTree::START_TAG) {
838 continue;
839 }
840 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700841 const char16_t* ctag16 = tree.getElementName(&len);
842 if (ctag16 == NULL) {
843 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
844 goto bail;
845 }
846 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -0800847 //printf("Depth %d tag %s\n", depth, tag.string());
848 if (depth == 1) {
849 if (tag != "manifest") {
850 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
851 goto bail;
852 }
853 String8 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -0700854 printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800855 } else if (depth == 2 && tag == "permission") {
856 String8 error;
857 String8 name = getAttribute(tree, NAME_ATTR, &error);
858 if (error != "") {
859 fprintf(stderr, "ERROR: %s\n", error.string());
860 goto bail;
861 }
Maurice Chu2675f762013-10-22 17:33:11 -0700862 printf("permission: %s\n",
863 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800864 } else if (depth == 2 && tag == "uses-permission") {
865 String8 error;
866 String8 name = getAttribute(tree, NAME_ATTR, &error);
867 if (error != "") {
868 fprintf(stderr, "ERROR: %s\n", error.string());
869 goto bail;
870 }
Adam Lesinski58f1f362013-11-12 12:59:08 -0800871 printUsesPermission(name,
872 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
873 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
Adam Lesinski282e1812014-01-23 18:17:42 -0800874 }
875 }
876 } else if (strcmp("badging", option) == 0) {
877 Vector<String8> locales;
878 res.getLocales(&locales);
879
880 Vector<ResTable_config> configs;
881 res.getConfigurations(&configs);
882 SortedVector<int> densities;
883 const size_t NC = configs.size();
884 for (size_t i=0; i<NC; i++) {
885 int dens = configs[i].density;
886 if (dens == 0) {
887 dens = 160;
888 }
889 densities.add(dens);
890 }
891
892 size_t len;
893 ResXMLTree::event_code_t code;
894 int depth = 0;
895 String8 error;
896 bool withinActivity = false;
897 bool isMainActivity = false;
898 bool isLauncherActivity = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800899 bool isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800900 bool isSearchable = false;
901 bool withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700902 bool withinSupportsInput = false;
Adam Lesinski2c72b682014-06-24 09:56:01 -0700903 bool withinFeatureGroup = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800904 bool withinReceiver = false;
905 bool withinService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700906 bool withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800907 bool withinIntentFilter = false;
908 bool hasMainActivity = false;
909 bool hasOtherActivities = false;
910 bool hasOtherReceivers = false;
911 bool hasOtherServices = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700912 bool hasIntentFilter = false;
913
Adam Lesinski282e1812014-01-23 18:17:42 -0800914 bool hasWallpaperService = false;
915 bool hasImeService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700916 bool hasAccessibilityService = false;
917 bool hasPrintService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800918 bool hasWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700919 bool hasDeviceAdminReceiver = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700920 bool hasPaymentService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700921 bool hasDocumentsProvider = false;
922 bool hasCameraActivity = false;
923 bool hasCameraSecureActivity = false;
924 bool hasLauncher = false;
925 bool hasNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400926 bool hasDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700927
Adam Lesinski282e1812014-01-23 18:17:42 -0800928 bool actMainActivity = false;
929 bool actWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700930 bool actDeviceAdminEnabled = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800931 bool actImeService = false;
932 bool actWallpaperService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700933 bool actAccessibilityService = false;
934 bool actPrintService = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700935 bool actHostApduService = false;
936 bool actOffHostApduService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700937 bool actDocumentsProvider = false;
938 bool actNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400939 bool actDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700940 bool actCamera = false;
941 bool actCameraSecure = false;
942 bool catLauncher = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700943 bool hasMetaHostPaymentCategory = false;
944 bool hasMetaOffHostPaymentCategory = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700945
946 // These permissions are required by services implementing services
947 // the system binds to (IME, Accessibility, PrintServices, etc.)
948 bool hasBindDeviceAdminPermission = false;
949 bool hasBindInputMethodPermission = false;
950 bool hasBindAccessibilityServicePermission = false;
951 bool hasBindPrintServicePermission = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700952 bool hasBindNfcServicePermission = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700953 bool hasRequiredSafAttributes = false;
954 bool hasBindNotificationListenerServicePermission = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400955 bool hasBindDreamServicePermission = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800956
957 // These two implement the implicit permissions that are granted
958 // to pre-1.6 applications.
959 bool hasWriteExternalStoragePermission = false;
960 bool hasReadPhoneStatePermission = false;
961
962 // If an app requests write storage, they will also get read storage.
963 bool hasReadExternalStoragePermission = false;
964
965 // Implement transition to read and write call log.
966 bool hasReadContactsPermission = false;
967 bool hasWriteContactsPermission = false;
968 bool hasReadCallLogPermission = false;
969 bool hasWriteCallLogPermission = false;
970
971 // This next group of variables is used to implement a group of
972 // backward-compatibility heuristics necessitated by the addition of
973 // some new uses-feature constants in 2.1 and 2.2. In most cases, the
974 // heuristic is "if an app requests a permission but doesn't explicitly
975 // request the corresponding <uses-feature>, presume it's there anyway".
Adam Lesinski2c72b682014-06-24 09:56:01 -0700976
Adam Lesinski282e1812014-01-23 18:17:42 -0800977 // 2.2 also added some other features that apps can request, but that
978 // have no corresponding permission, so we cannot implement any
979 // back-compatibility heuristic for them. The below are thus unnecessary
980 // (but are retained here for documentary purposes.)
981 //bool specCompassFeature = false;
982 //bool specAccelerometerFeature = false;
983 //bool specProximityFeature = false;
984 //bool specAmbientLightFeature = false;
985 //bool specLiveWallpaperFeature = false;
986
987 int targetSdk = 0;
988 int smallScreen = 1;
989 int normalScreen = 1;
990 int largeScreen = 1;
991 int xlargeScreen = 1;
992 int anyDensity = 1;
993 int requiresSmallestWidthDp = 0;
994 int compatibleWidthLimitDp = 0;
995 int largestWidthLimitDp = 0;
996 String8 pkg;
997 String8 activityName;
998 String8 activityLabel;
999 String8 activityIcon;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001000 String8 activityBanner;
Adam Lesinski282e1812014-01-23 18:17:42 -08001001 String8 receiverName;
1002 String8 serviceName;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001003 Vector<String8> supportedInput;
Adam Lesinski2c72b682014-06-24 09:56:01 -07001004
1005 FeatureGroup commonFeatures;
1006 Vector<FeatureGroup> featureGroups;
1007 KeyedVector<String8, ImpliedFeature> impliedFeatures;
1008
Adam Lesinski282e1812014-01-23 18:17:42 -08001009 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1010 if (code == ResXMLTree::END_TAG) {
1011 depth--;
1012 if (depth < 2) {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001013 if (withinSupportsInput && !supportedInput.isEmpty()) {
1014 printf("supports-input: '");
1015 const size_t N = supportedInput.size();
1016 for (size_t i=0; i<N; i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07001017 printf("%s", ResTable::normalizeForOutput(
1018 supportedInput[i].string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001019 if (i != N - 1) {
1020 printf("' '");
1021 } else {
1022 printf("'\n");
1023 }
1024 }
1025 supportedInput.clear();
1026 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001027 withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001028 withinSupportsInput = false;
Adam Lesinski2c72b682014-06-24 09:56:01 -07001029 withinFeatureGroup = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001030 } else if (depth < 3) {
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001031 if (withinActivity && isMainActivity) {
Maurice Chu2675f762013-10-22 17:33:11 -07001032 String8 aName(getComponentName(pkg, activityName));
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001033 if (isLauncherActivity) {
1034 printf("launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001035 if (aName.length() > 0) {
1036 printf(" name='%s' ",
1037 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001038 }
1039 printf(" label='%s' icon='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001040 ResTable::normalizeForOutput(activityLabel.string()).string(),
1041 ResTable::normalizeForOutput(activityIcon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001042 }
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001043 if (isLeanbackLauncherActivity) {
1044 printf("leanback-launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001045 if (aName.length() > 0) {
1046 printf(" name='%s' ",
1047 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001048 }
1049 printf(" label='%s' icon='%s' banner='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001050 ResTable::normalizeForOutput(activityLabel.string()).string(),
1051 ResTable::normalizeForOutput(activityIcon.string()).string(),
1052 ResTable::normalizeForOutput(activityBanner.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001053 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001054 }
1055 if (!hasIntentFilter) {
1056 hasOtherActivities |= withinActivity;
1057 hasOtherReceivers |= withinReceiver;
1058 hasOtherServices |= withinService;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001059 } else {
1060 if (withinService) {
1061 hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
1062 hasBindNfcServicePermission);
1063 hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
1064 hasBindNfcServicePermission);
1065 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001066 }
1067 withinActivity = false;
1068 withinService = false;
1069 withinReceiver = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001070 withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001071 hasIntentFilter = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001072 isMainActivity = isLauncherActivity = isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001073 } else if (depth < 4) {
1074 if (withinIntentFilter) {
1075 if (withinActivity) {
1076 hasMainActivity |= actMainActivity;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001077 hasLauncher |= catLauncher;
1078 hasCameraActivity |= actCamera;
1079 hasCameraSecureActivity |= actCameraSecure;
1080 hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure;
Adam Lesinski282e1812014-01-23 18:17:42 -08001081 } else if (withinReceiver) {
1082 hasWidgetReceivers |= actWidgetReceivers;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001083 hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
1084 hasBindDeviceAdminPermission);
1085 hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
Adam Lesinski282e1812014-01-23 18:17:42 -08001086 } else if (withinService) {
1087 hasImeService |= actImeService;
1088 hasWallpaperService |= actWallpaperService;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001089 hasAccessibilityService |= (actAccessibilityService &&
1090 hasBindAccessibilityServicePermission);
1091 hasPrintService |= (actPrintService && hasBindPrintServicePermission);
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001092 hasNotificationListenerService |= actNotificationListenerService &&
1093 hasBindNotificationListenerServicePermission;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001094 hasDreamService |= actDreamService && hasBindDreamServicePermission;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001095 hasOtherServices |= (!actImeService && !actWallpaperService &&
Adam Lesinski94fc9122013-09-30 17:16:09 -07001096 !actAccessibilityService && !actPrintService &&
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001097 !actHostApduService && !actOffHostApduService &&
1098 !actNotificationListenerService);
1099 } else if (withinProvider) {
1100 hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes;
Adam Lesinski282e1812014-01-23 18:17:42 -08001101 }
1102 }
1103 withinIntentFilter = false;
1104 }
1105 continue;
1106 }
1107 if (code != ResXMLTree::START_TAG) {
1108 continue;
1109 }
1110 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001111
1112 const char16_t* ctag16 = tree.getElementName(&len);
1113 if (ctag16 == NULL) {
1114 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
1115 goto bail;
1116 }
1117 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -08001118 //printf("Depth %d, %s\n", depth, tag.string());
1119 if (depth == 1) {
1120 if (tag != "manifest") {
1121 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
1122 goto bail;
1123 }
1124 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -07001125 printf("package: name='%s' ",
1126 ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001127 int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
1128 if (error != "") {
1129 fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
1130 goto bail;
1131 }
1132 if (versionCode > 0) {
1133 printf("versionCode='%d' ", versionCode);
1134 } else {
1135 printf("versionCode='' ");
1136 }
1137 String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
1138 if (error != "") {
1139 fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
1140 goto bail;
1141 }
Maurice Chu2675f762013-10-22 17:33:11 -07001142 printf("versionName='%s'\n",
1143 ResTable::normalizeForOutput(versionName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001144 } else if (depth == 2) {
1145 withinApplication = false;
1146 if (tag == "application") {
1147 withinApplication = true;
1148
1149 String8 label;
1150 const size_t NL = locales.size();
1151 for (size_t i=0; i<NL; i++) {
1152 const char* localeStr = locales[i].string();
1153 assets.setLocale(localeStr != NULL ? localeStr : "");
1154 String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1155 if (llabel != "") {
1156 if (localeStr == NULL || strlen(localeStr) == 0) {
1157 label = llabel;
Maurice Chu2675f762013-10-22 17:33:11 -07001158 printf("application-label:'%s'\n",
1159 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001160 } else {
1161 if (label == "") {
1162 label = llabel;
1163 }
1164 printf("application-label-%s:'%s'\n", localeStr,
Maurice Chu2675f762013-10-22 17:33:11 -07001165 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001166 }
1167 }
1168 }
1169
1170 ResTable_config tmpConfig = config;
1171 const size_t ND = densities.size();
1172 for (size_t i=0; i<ND; i++) {
1173 tmpConfig.density = densities[i];
1174 assets.setConfiguration(tmpConfig);
1175 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1176 if (icon != "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001177 printf("application-icon-%d:'%s'\n", densities[i],
1178 ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001179 }
1180 }
1181 assets.setConfiguration(config);
1182
1183 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1184 if (error != "") {
1185 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
1186 goto bail;
1187 }
1188 int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
1189 if (error != "") {
1190 fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
1191 goto bail;
1192 }
Maurice Chu2675f762013-10-22 17:33:11 -07001193 printf("application: label='%s' ",
1194 ResTable::normalizeForOutput(label.string()).string());
1195 printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001196 if (testOnly != 0) {
1197 printf("testOnly='%d'\n", testOnly);
1198 }
1199
1200 int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
1201 if (error != "") {
1202 fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
1203 goto bail;
1204 }
1205 if (debuggable != 0) {
1206 printf("application-debuggable\n");
1207 }
1208 } else if (tag == "uses-sdk") {
1209 int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
1210 if (error != "") {
1211 error = "";
1212 String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
1213 if (error != "") {
1214 fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
1215 error.string());
1216 goto bail;
1217 }
1218 if (name == "Donut") targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001219 printf("sdkVersion:'%s'\n",
1220 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001221 } else if (code != -1) {
1222 targetSdk = code;
1223 printf("sdkVersion:'%d'\n", code);
1224 }
1225 code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
1226 if (code != -1) {
1227 printf("maxSdkVersion:'%d'\n", code);
1228 }
1229 code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
1230 if (error != "") {
1231 error = "";
1232 String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
1233 if (error != "") {
1234 fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
1235 error.string());
1236 goto bail;
1237 }
1238 if (name == "Donut" && targetSdk < 4) targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001239 printf("targetSdkVersion:'%s'\n",
1240 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001241 } else if (code != -1) {
1242 if (targetSdk < code) {
1243 targetSdk = code;
1244 }
1245 printf("targetSdkVersion:'%d'\n", code);
1246 }
1247 } else if (tag == "uses-configuration") {
1248 int32_t reqTouchScreen = getIntegerAttribute(tree,
1249 REQ_TOUCH_SCREEN_ATTR, NULL, 0);
1250 int32_t reqKeyboardType = getIntegerAttribute(tree,
1251 REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
1252 int32_t reqHardKeyboard = getIntegerAttribute(tree,
1253 REQ_HARD_KEYBOARD_ATTR, NULL, 0);
1254 int32_t reqNavigation = getIntegerAttribute(tree,
1255 REQ_NAVIGATION_ATTR, NULL, 0);
1256 int32_t reqFiveWayNav = getIntegerAttribute(tree,
1257 REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
1258 printf("uses-configuration:");
1259 if (reqTouchScreen != 0) {
1260 printf(" reqTouchScreen='%d'", reqTouchScreen);
1261 }
1262 if (reqKeyboardType != 0) {
1263 printf(" reqKeyboardType='%d'", reqKeyboardType);
1264 }
1265 if (reqHardKeyboard != 0) {
1266 printf(" reqHardKeyboard='%d'", reqHardKeyboard);
1267 }
1268 if (reqNavigation != 0) {
1269 printf(" reqNavigation='%d'", reqNavigation);
1270 }
1271 if (reqFiveWayNav != 0) {
1272 printf(" reqFiveWayNav='%d'", reqFiveWayNav);
1273 }
1274 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001275 } else if (tag == "supports-input") {
1276 withinSupportsInput = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001277 } else if (tag == "supports-screens") {
1278 smallScreen = getIntegerAttribute(tree,
1279 SMALL_SCREEN_ATTR, NULL, 1);
1280 normalScreen = getIntegerAttribute(tree,
1281 NORMAL_SCREEN_ATTR, NULL, 1);
1282 largeScreen = getIntegerAttribute(tree,
1283 LARGE_SCREEN_ATTR, NULL, 1);
1284 xlargeScreen = getIntegerAttribute(tree,
1285 XLARGE_SCREEN_ATTR, NULL, 1);
1286 anyDensity = getIntegerAttribute(tree,
1287 ANY_DENSITY_ATTR, NULL, 1);
1288 requiresSmallestWidthDp = getIntegerAttribute(tree,
1289 REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
1290 compatibleWidthLimitDp = getIntegerAttribute(tree,
1291 COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1292 largestWidthLimitDp = getIntegerAttribute(tree,
1293 LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
Adam Lesinski2c72b682014-06-24 09:56:01 -07001294 } else if (tag == "feature-group") {
1295 withinFeatureGroup = true;
1296 FeatureGroup group;
1297 group.label = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1298 if (error != "") {
1299 fprintf(stderr, "ERROR getting 'android:label' attribute:"
1300 " %s\n", error.string());
1301 goto bail;
1302 }
1303 featureGroups.add(group);
1304
Adam Lesinski282e1812014-01-23 18:17:42 -08001305 } else if (tag == "uses-feature") {
1306 String8 name = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001307 if (name != "" && error == "") {
1308 int req = getIntegerAttribute(tree,
1309 REQUIRED_ATTR, NULL, 1);
1310
Adam Lesinski2c72b682014-06-24 09:56:01 -07001311 commonFeatures.features.add(name, req);
1312 if (req) {
1313 addParentFeatures(&commonFeatures, name);
Adam Lesinski282e1812014-01-23 18:17:42 -08001314 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001315 } else {
1316 int vers = getIntegerAttribute(tree,
1317 GL_ES_VERSION_ATTR, &error);
1318 if (error == "") {
1319 printf("uses-gl-es:'0x%x'\n", vers);
1320 }
1321 }
1322 } else if (tag == "uses-permission") {
1323 String8 name = getAttribute(tree, NAME_ATTR, &error);
1324 if (name != "" && error == "") {
1325 if (name == "android.permission.CAMERA") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001326 addImpliedFeature(&impliedFeatures, "android.hardware.feature",
1327 String8::format("requested %s permission", name.string())
1328 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001329 } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001330 addImpliedFeature(&impliedFeatures, "android.hardware.location.gps",
1331 String8::format("requested %s permission", name.string())
1332 .string());
1333 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1334 String8::format("requested %s permission", name.string())
1335 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001336 } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001337 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1338 String8::format("requested %s permission", name.string())
1339 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001340 } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001341 addImpliedFeature(&impliedFeatures, "android.hardware.location.network",
1342 String8::format("requested %s permission", name.string())
1343 .string());
1344 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1345 String8::format("requested %s permission", name.string())
1346 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001347 } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
1348 name == "android.permission.INSTALL_LOCATION_PROVIDER") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001349 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1350 String8::format("requested %s permission", name.string())
1351 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001352 } else if (name == "android.permission.BLUETOOTH" ||
1353 name == "android.permission.BLUETOOTH_ADMIN") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001354 if (targetSdk > 4) {
1355 addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
1356 String8::format("requested %s permission", name.string())
1357 .string());
1358 addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
1359 "targetSdkVersion > 4");
1360 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001361 } else if (name == "android.permission.RECORD_AUDIO") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001362 addImpliedFeature(&impliedFeatures, "android.hardware.microphone",
1363 String8::format("requested %s permission", name.string())
1364 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001365 } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
1366 name == "android.permission.CHANGE_WIFI_STATE" ||
1367 name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001368 addImpliedFeature(&impliedFeatures, "android.hardware.wifi",
1369 String8::format("requested %s permission", name.string())
1370 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001371 } else if (name == "android.permission.CALL_PHONE" ||
1372 name == "android.permission.CALL_PRIVILEGED" ||
1373 name == "android.permission.MODIFY_PHONE_STATE" ||
1374 name == "android.permission.PROCESS_OUTGOING_CALLS" ||
1375 name == "android.permission.READ_SMS" ||
1376 name == "android.permission.RECEIVE_SMS" ||
1377 name == "android.permission.RECEIVE_MMS" ||
1378 name == "android.permission.RECEIVE_WAP_PUSH" ||
1379 name == "android.permission.SEND_SMS" ||
1380 name == "android.permission.WRITE_APN_SETTINGS" ||
1381 name == "android.permission.WRITE_SMS") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001382 addImpliedFeature(&impliedFeatures, "android.hardware.telephony",
1383 String8("requested a telephony permission").string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001384 } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
1385 hasWriteExternalStoragePermission = true;
1386 } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
1387 hasReadExternalStoragePermission = true;
1388 } else if (name == "android.permission.READ_PHONE_STATE") {
1389 hasReadPhoneStatePermission = true;
1390 } else if (name == "android.permission.READ_CONTACTS") {
1391 hasReadContactsPermission = true;
1392 } else if (name == "android.permission.WRITE_CONTACTS") {
1393 hasWriteContactsPermission = true;
1394 } else if (name == "android.permission.READ_CALL_LOG") {
1395 hasReadCallLogPermission = true;
1396 } else if (name == "android.permission.WRITE_CALL_LOG") {
1397 hasWriteCallLogPermission = true;
1398 }
Adam Lesinski58f1f362013-11-12 12:59:08 -08001399
1400 printUsesPermission(name,
1401 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
1402 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
1403 } else {
Adam Lesinski282e1812014-01-23 18:17:42 -08001404 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1405 error.string());
1406 goto bail;
1407 }
1408 } else if (tag == "uses-package") {
1409 String8 name = getAttribute(tree, NAME_ATTR, &error);
1410 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001411 printf("uses-package:'%s'\n",
1412 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001413 } else {
1414 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1415 error.string());
1416 goto bail;
1417 }
1418 } else if (tag == "original-package") {
1419 String8 name = getAttribute(tree, NAME_ATTR, &error);
1420 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001421 printf("original-package:'%s'\n",
1422 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001423 } else {
1424 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1425 error.string());
1426 goto bail;
1427 }
1428 } else if (tag == "supports-gl-texture") {
1429 String8 name = getAttribute(tree, NAME_ATTR, &error);
1430 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001431 printf("supports-gl-texture:'%s'\n",
1432 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001433 } else {
1434 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1435 error.string());
1436 goto bail;
1437 }
1438 } else if (tag == "compatible-screens") {
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001439 printCompatibleScreens(tree, &error);
1440 if (error != "") {
1441 fprintf(stderr, "ERROR getting compatible screens: %s\n",
1442 error.string());
1443 goto bail;
1444 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001445 depth--;
1446 } else if (tag == "package-verifier") {
1447 String8 name = getAttribute(tree, NAME_ATTR, &error);
1448 if (name != "" && error == "") {
1449 String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
1450 if (publicKey != "" && error == "") {
1451 printf("package-verifier: name='%s' publicKey='%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001452 ResTable::normalizeForOutput(name.string()).string(),
1453 ResTable::normalizeForOutput(publicKey.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001454 }
1455 }
1456 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001457 } else if (depth == 3) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001458 withinActivity = false;
1459 withinReceiver = false;
1460 withinService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001461 withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001462 hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001463 hasMetaHostPaymentCategory = false;
1464 hasMetaOffHostPaymentCategory = false;
1465 hasBindDeviceAdminPermission = false;
1466 hasBindInputMethodPermission = false;
1467 hasBindAccessibilityServicePermission = false;
1468 hasBindPrintServicePermission = false;
1469 hasBindNfcServicePermission = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001470 hasRequiredSafAttributes = false;
1471 hasBindNotificationListenerServicePermission = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001472 hasBindDreamServicePermission = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001473 if (withinApplication) {
1474 if(tag == "activity") {
1475 withinActivity = true;
1476 activityName = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001477 if (error != "") {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001478 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1479 error.string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001480 goto bail;
1481 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001482
Michael Wrightec4fdec2013-09-06 16:50:52 -07001483 activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1484 if (error != "") {
1485 fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
1486 error.string());
1487 goto bail;
1488 }
1489
1490 activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1491 if (error != "") {
1492 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
1493 error.string());
1494 goto bail;
1495 }
1496
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001497 activityBanner = getResolvedAttribute(&res, tree, BANNER_ATTR, &error);
1498 if (error != "") {
1499 fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
1500 error.string());
1501 goto bail;
1502 }
1503
Michael Wrightec4fdec2013-09-06 16:50:52 -07001504 int32_t orien = getResolvedIntegerAttribute(&res, tree,
1505 SCREEN_ORIENTATION_ATTR, &error);
1506 if (error == "") {
1507 if (orien == 0 || orien == 6 || orien == 8) {
1508 // Requests landscape, sensorLandscape, or reverseLandscape.
Adam Lesinski2c72b682014-06-24 09:56:01 -07001509 addImpliedFeature(&impliedFeatures, "android.hardware.screen.landscape",
1510 "one or more activities have specified a landscape orientation");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001511 } else if (orien == 1 || orien == 7 || orien == 9) {
1512 // Requests portrait, sensorPortrait, or reversePortrait.
Adam Lesinski2c72b682014-06-24 09:56:01 -07001513 addImpliedFeature(&impliedFeatures, "android.hardware.screen.portrait",
1514 "one or more activities have specified a portrait orientation");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001515 }
1516 }
1517 } else if (tag == "uses-library") {
1518 String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
1519 if (error != "") {
1520 fprintf(stderr,
1521 "ERROR getting 'android:name' attribute for uses-library"
1522 " %s\n", error.string());
1523 goto bail;
1524 }
1525 int req = getIntegerAttribute(tree,
1526 REQUIRED_ATTR, NULL, 1);
1527 printf("uses-library%s:'%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001528 req ? "" : "-not-required", ResTable::normalizeForOutput(
1529 libraryName.string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001530 } else if (tag == "receiver") {
1531 withinReceiver = true;
1532 receiverName = getAttribute(tree, NAME_ATTR, &error);
1533
1534 if (error != "") {
1535 fprintf(stderr,
1536 "ERROR getting 'android:name' attribute for receiver:"
1537 " %s\n", error.string());
1538 goto bail;
1539 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001540
1541 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1542 if (error == "") {
1543 if (permission == "android.permission.BIND_DEVICE_ADMIN") {
1544 hasBindDeviceAdminPermission = true;
1545 }
1546 } else {
1547 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1548 " receiver '%s': %s\n", receiverName.string(), error.string());
1549 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001550 } else if (tag == "service") {
1551 withinService = true;
1552 serviceName = getAttribute(tree, NAME_ATTR, &error);
1553
1554 if (error != "") {
1555 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1556 "service:%s\n", error.string());
1557 goto bail;
1558 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001559
1560 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1561 if (error == "") {
1562 if (permission == "android.permission.BIND_INPUT_METHOD") {
1563 hasBindInputMethodPermission = true;
1564 } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
1565 hasBindAccessibilityServicePermission = true;
1566 } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
1567 hasBindPrintServicePermission = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001568 } else if (permission == "android.permission.BIND_NFC_SERVICE") {
1569 hasBindNfcServicePermission = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001570 } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
1571 hasBindNotificationListenerServicePermission = true;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001572 } else if (permission == "android.permission.BIND_DREAM_SERVICE") {
1573 hasBindDreamServicePermission = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001574 }
1575 } else {
1576 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1577 " service '%s': %s\n", serviceName.string(), error.string());
1578 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001579 } else if (tag == "provider") {
1580 withinProvider = true;
1581
1582 bool exported = getResolvedIntegerAttribute(&res, tree, EXPORTED_ATTR, &error);
1583 if (error != "") {
1584 fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:"
1585 " %s\n", error.string());
1586 goto bail;
1587 }
1588
1589 bool grantUriPermissions = getResolvedIntegerAttribute(&res, tree,
1590 GRANT_URI_PERMISSIONS_ATTR, &error);
1591 if (error != "") {
1592 fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:"
1593 " %s\n", error.string());
1594 goto bail;
1595 }
1596
1597 String8 permission = getResolvedAttribute(&res, tree, PERMISSION_ATTR, &error);
1598 if (error != "") {
1599 fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:"
1600 " %s\n", error.string());
1601 goto bail;
1602 }
1603
1604 hasRequiredSafAttributes |= exported && grantUriPermissions &&
1605 permission == "android.permission.MANAGE_DOCUMENTS";
1606
Michael Wrightec4fdec2013-09-06 16:50:52 -07001607 } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
Adam Lesinskib71adb62014-05-15 14:14:41 -07001608 String8 metaDataName = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001609 if (error != "") {
1610 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1611 "meta-data:%s\n", error.string());
1612 goto bail;
1613 }
Maurice Chu2675f762013-10-22 17:33:11 -07001614 printf("meta-data: name='%s' ",
1615 ResTable::normalizeForOutput(metaDataName.string()).string());
Maurice Chu76327312013-10-16 18:28:46 -07001616 printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"),
1617 &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001618 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001619 // Try looking for a RESOURCE_ATTR
1620 error = "";
1621 printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR,
1622 String8("resource"), &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001623 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001624 fprintf(stderr, "ERROR getting 'android:value' or "
1625 "'android:resource' attribute for "
1626 "meta-data:%s\n", error.string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001627 goto bail;
1628 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001629 }
Maurice Chu76327312013-10-16 18:28:46 -07001630 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001631 } else if (withinSupportsInput && tag == "input-type") {
1632 String8 name = getAttribute(tree, NAME_ATTR, &error);
1633 if (name != "" && error == "") {
1634 supportedInput.add(name);
1635 } else {
1636 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1637 error.string());
1638 goto bail;
1639 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001640 }
Adam Lesinski2c72b682014-06-24 09:56:01 -07001641 } else if (withinFeatureGroup && tag == "uses-feature") {
1642 String8 name = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
1643 if (error != "") {
1644 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1645 error.string());
1646 goto bail;
1647 }
1648
1649 int required = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
1650 FeatureGroup& top = featureGroups.editTop();
1651 top.features.add(name, required);
1652 if (required) {
1653 addParentFeatures(&top, name);
1654 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001655 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001656 } else if (depth == 4) {
1657 if (tag == "intent-filter") {
1658 hasIntentFilter = true;
1659 withinIntentFilter = true;
1660 actMainActivity = false;
1661 actWidgetReceivers = false;
1662 actImeService = false;
1663 actWallpaperService = false;
1664 actAccessibilityService = false;
1665 actPrintService = false;
1666 actDeviceAdminEnabled = false;
1667 actHostApduService = false;
1668 actOffHostApduService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001669 actDocumentsProvider = false;
1670 actNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001671 actDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001672 actCamera = false;
1673 actCameraSecure = false;
1674 catLauncher = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001675 } else if (withinService && tag == "meta-data") {
1676 String8 name = getAttribute(tree, NAME_ATTR, &error);
1677 if (error != "") {
1678 fprintf(stderr, "ERROR getting 'android:name' attribute for"
1679 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1680 goto bail;
1681 }
1682
1683 if (name == "android.nfc.cardemulation.host_apdu_service" ||
1684 name == "android.nfc.cardemulation.off_host_apdu_service") {
1685 bool offHost = true;
1686 if (name == "android.nfc.cardemulation.host_apdu_service") {
1687 offHost = false;
1688 }
1689
1690 String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
1691 if (error != "") {
1692 fprintf(stderr, "ERROR getting 'android:resource' attribute for"
1693 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1694 goto bail;
1695 }
1696
1697 Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
1698 offHost, &error);
1699 if (error != "") {
1700 fprintf(stderr, "ERROR getting AID category for service '%s'\n",
1701 serviceName.string());
1702 goto bail;
1703 }
1704
1705 const size_t catLen = categories.size();
1706 for (size_t i = 0; i < catLen; i++) {
1707 bool paymentCategory = (categories[i] == "payment");
1708 if (offHost) {
1709 hasMetaOffHostPaymentCategory |= paymentCategory;
1710 } else {
1711 hasMetaHostPaymentCategory |= paymentCategory;
1712 }
1713 }
1714 }
1715 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001716 } else if ((depth == 5) && withinIntentFilter) {
1717 String8 action;
1718 if (tag == "action") {
1719 action = getAttribute(tree, NAME_ATTR, &error);
1720 if (error != "") {
1721 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1722 error.string());
1723 goto bail;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001724 }
1725
Adam Lesinskia5018c92013-09-30 16:23:15 -07001726 if (withinActivity) {
1727 if (action == "android.intent.action.MAIN") {
1728 isMainActivity = true;
1729 actMainActivity = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001730 } else if (action == "android.media.action.STILL_IMAGE_CAMERA" ||
1731 action == "android.media.action.VIDEO_CAMERA") {
1732 actCamera = true;
1733 } else if (action == "android.media.action.STILL_IMAGE_CAMERA_SECURE") {
1734 actCameraSecure = true;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001735 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001736 } else if (withinReceiver) {
1737 if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
1738 actWidgetReceivers = true;
1739 } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
1740 actDeviceAdminEnabled = true;
1741 }
1742 } else if (withinService) {
1743 if (action == "android.view.InputMethod") {
1744 actImeService = true;
1745 } else if (action == "android.service.wallpaper.WallpaperService") {
1746 actWallpaperService = true;
1747 } else if (action == "android.accessibilityservice.AccessibilityService") {
1748 actAccessibilityService = true;
1749 } else if (action == "android.printservice.PrintService") {
1750 actPrintService = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001751 } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
1752 actHostApduService = true;
1753 } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
1754 actOffHostApduService = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001755 } else if (action == "android.service.notification.NotificationListenerService") {
1756 actNotificationListenerService = true;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001757 } else if (action == "android.service.dreams.DreamService") {
1758 actDreamService = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001759 }
1760 } else if (withinProvider) {
1761 if (action == "android.content.action.DOCUMENTS_PROVIDER") {
1762 actDocumentsProvider = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001763 }
1764 }
1765 if (action == "android.intent.action.SEARCH") {
1766 isSearchable = true;
1767 }
1768 }
1769
1770 if (tag == "category") {
1771 String8 category = getAttribute(tree, NAME_ATTR, &error);
1772 if (error != "") {
1773 fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
1774 error.string());
1775 goto bail;
1776 }
1777 if (withinActivity) {
1778 if (category == "android.intent.category.LAUNCHER") {
1779 isLauncherActivity = true;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001780 } else if (category == "android.intent.category.LEANBACK_LAUNCHER") {
1781 isLeanbackLauncherActivity = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001782 } else if (category == "android.intent.category.HOME") {
1783 catLauncher = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001784 }
1785 }
1786 }
1787 }
1788 }
1789
1790 // Pre-1.6 implicitly granted permission compatibility logic
1791 if (targetSdk < 4) {
1792 if (!hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001793 printUsesPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"));
1794 printUsesImpliedPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"),
1795 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001796 hasWriteExternalStoragePermission = true;
1797 }
1798 if (!hasReadPhoneStatePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001799 printUsesPermission(String8("android.permission.READ_PHONE_STATE"));
1800 printUsesImpliedPermission(String8("android.permission.READ_PHONE_STATE"),
1801 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001802 }
1803 }
1804
1805 // If the application has requested WRITE_EXTERNAL_STORAGE, we will
1806 // force them to always take READ_EXTERNAL_STORAGE as well. We always
1807 // do this (regardless of target API version) because we can't have
1808 // an app with write permission but not read permission.
1809 if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001810 printUsesPermission(String8("android.permission.READ_EXTERNAL_STORAGE"));
1811 printUsesImpliedPermission(String8("android.permission.READ_EXTERNAL_STORAGE"),
1812 String8("requested WRITE_EXTERNAL_STORAGE"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001813 }
1814
1815 // Pre-JellyBean call log permission compatibility.
1816 if (targetSdk < 16) {
1817 if (!hasReadCallLogPermission && hasReadContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001818 printUsesPermission(String8("android.permission.READ_CALL_LOG"));
1819 printUsesImpliedPermission(String8("android.permission.READ_CALL_LOG"),
1820 String8("targetSdkVersion < 16 and requested READ_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001821 }
1822 if (!hasWriteCallLogPermission && hasWriteContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001823 printUsesPermission(String8("android.permission.WRITE_CALL_LOG"));
1824 printUsesImpliedPermission(String8("android.permission.WRITE_CALL_LOG"),
1825 String8("targetSdkVersion < 16 and requested WRITE_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001826 }
1827 }
1828
Adam Lesinski2c72b682014-06-24 09:56:01 -07001829 addImpliedFeature(&impliedFeatures, "android.hardware.touchscreen",
1830 "default feature for all apps");
1831
1832 const size_t numFeatureGroups = featureGroups.size();
1833 if (numFeatureGroups == 0) {
1834 // If no <feature-group> tags were defined, apply auto-implied features.
1835 printFeatureGroup(commonFeatures, &impliedFeatures);
1836
1837 } else {
1838 // <feature-group> tags are defined, so we ignore implied features and
1839 for (size_t i = 0; i < numFeatureGroups; i++) {
1840 FeatureGroup& grp = featureGroups.editItemAt(i);
1841
1842 // Merge the features defined in the top level (not inside a <feature-group>)
1843 // with this feature group.
1844 const size_t numCommonFeatures = commonFeatures.features.size();
1845 for (size_t j = 0; j < numCommonFeatures; j++) {
1846 if (grp.features.indexOfKey(commonFeatures.features.keyAt(j)) < 0) {
1847 grp.features.add(commonFeatures.features.keyAt(j), commonFeatures.features[j]);
1848 }
1849 }
1850
1851 if (!grp.features.isEmpty()) {
1852 printFeatureGroup(grp);
Adam Lesinski282e1812014-01-23 18:17:42 -08001853 }
1854 }
1855 }
1856
Adam Lesinski282e1812014-01-23 18:17:42 -08001857
Adam Lesinski282e1812014-01-23 18:17:42 -08001858 if (hasWidgetReceivers) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001859 printComponentPresence("app-widget");
Adam Lesinski282e1812014-01-23 18:17:42 -08001860 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001861 if (hasDeviceAdminReceiver) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001862 printComponentPresence("device-admin");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001863 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001864 if (hasImeService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001865 printComponentPresence("ime");
Adam Lesinski282e1812014-01-23 18:17:42 -08001866 }
1867 if (hasWallpaperService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001868 printComponentPresence("wallpaper");
Adam Lesinski282e1812014-01-23 18:17:42 -08001869 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001870 if (hasAccessibilityService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001871 printComponentPresence("accessibility");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001872 }
1873 if (hasPrintService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001874 printComponentPresence("print-service");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001875 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001876 if (hasPaymentService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001877 printComponentPresence("payment");
1878 }
1879 if (isSearchable) {
1880 printComponentPresence("search");
1881 }
1882 if (hasDocumentsProvider) {
1883 printComponentPresence("document-provider");
1884 }
1885 if (hasLauncher) {
1886 printComponentPresence("launcher");
1887 }
1888 if (hasNotificationListenerService) {
1889 printComponentPresence("notification-listener");
1890 }
John Spurlockeb8d1be2014-06-25 17:46:15 -04001891 if (hasDreamService) {
1892 printComponentPresence("dream");
1893 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001894 if (hasCameraActivity) {
1895 printComponentPresence("camera");
1896 }
1897 if (hasCameraSecureActivity) {
1898 printComponentPresence("camera-secure");
1899 }
1900
1901 if (hasMainActivity) {
1902 printf("main\n");
Adam Lesinski94fc9122013-09-30 17:16:09 -07001903 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001904 if (hasOtherActivities) {
1905 printf("other-activities\n");
1906 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001907 if (hasOtherReceivers) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001908 printf("other-receivers\n");
1909 }
1910 if (hasOtherServices) {
1911 printf("other-services\n");
1912 }
1913
1914 // For modern apps, if screen size buckets haven't been specified
1915 // but the new width ranges have, then infer the buckets from them.
1916 if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0
1917 && requiresSmallestWidthDp > 0) {
1918 int compatWidth = compatibleWidthLimitDp;
1919 if (compatWidth <= 0) {
1920 compatWidth = requiresSmallestWidthDp;
1921 }
1922 if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) {
1923 smallScreen = -1;
1924 } else {
1925 smallScreen = 0;
1926 }
1927 if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) {
1928 normalScreen = -1;
1929 } else {
1930 normalScreen = 0;
1931 }
1932 if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) {
1933 largeScreen = -1;
1934 } else {
1935 largeScreen = 0;
1936 }
1937 if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) {
1938 xlargeScreen = -1;
1939 } else {
1940 xlargeScreen = 0;
1941 }
1942 }
1943
1944 // Determine default values for any unspecified screen sizes,
1945 // based on the target SDK of the package. As of 4 (donut)
1946 // the screen size support was introduced, so all default to
1947 // enabled.
1948 if (smallScreen > 0) {
1949 smallScreen = targetSdk >= 4 ? -1 : 0;
1950 }
1951 if (normalScreen > 0) {
1952 normalScreen = -1;
1953 }
1954 if (largeScreen > 0) {
1955 largeScreen = targetSdk >= 4 ? -1 : 0;
1956 }
1957 if (xlargeScreen > 0) {
1958 // Introduced in Gingerbread.
1959 xlargeScreen = targetSdk >= 9 ? -1 : 0;
1960 }
1961 if (anyDensity > 0) {
1962 anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0
1963 || compatibleWidthLimitDp > 0) ? -1 : 0;
1964 }
1965 printf("supports-screens:");
1966 if (smallScreen != 0) {
1967 printf(" 'small'");
1968 }
1969 if (normalScreen != 0) {
1970 printf(" 'normal'");
1971 }
1972 if (largeScreen != 0) {
1973 printf(" 'large'");
1974 }
1975 if (xlargeScreen != 0) {
1976 printf(" 'xlarge'");
1977 }
1978 printf("\n");
1979 printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
1980 if (requiresSmallestWidthDp > 0) {
1981 printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp);
1982 }
1983 if (compatibleWidthLimitDp > 0) {
1984 printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp);
1985 }
1986 if (largestWidthLimitDp > 0) {
1987 printf("largest-width-limit:'%d'\n", largestWidthLimitDp);
1988 }
1989
1990 printf("locales:");
1991 const size_t NL = locales.size();
1992 for (size_t i=0; i<NL; i++) {
1993 const char* localeStr = locales[i].string();
1994 if (localeStr == NULL || strlen(localeStr) == 0) {
1995 localeStr = "--_--";
1996 }
1997 printf(" '%s'", localeStr);
1998 }
1999 printf("\n");
2000
2001 printf("densities:");
2002 const size_t ND = densities.size();
2003 for (size_t i=0; i<ND; i++) {
2004 printf(" '%d'", densities[i]);
2005 }
2006 printf("\n");
2007
2008 AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
2009 if (dir != NULL) {
2010 if (dir->getFileCount() > 0) {
2011 printf("native-code:");
2012 for (size_t i=0; i<dir->getFileCount(); i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07002013 printf(" '%s'", ResTable::normalizeForOutput(
2014 dir->getFileName(i).string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002015 }
2016 printf("\n");
2017 }
2018 delete dir;
2019 }
2020 } else if (strcmp("badger", option) == 0) {
2021 printf("%s", CONSOLE_DATA);
2022 } else if (strcmp("configurations", option) == 0) {
2023 Vector<ResTable_config> configs;
2024 res.getConfigurations(&configs);
2025 const size_t N = configs.size();
2026 for (size_t i=0; i<N; i++) {
2027 printf("%s\n", configs[i].toString().string());
2028 }
2029 } else {
2030 fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
2031 goto bail;
2032 }
2033 }
2034
2035 result = NO_ERROR;
2036
2037bail:
2038 if (asset) {
2039 delete asset;
2040 }
2041 return (result != NO_ERROR);
2042}
2043
2044
2045/*
2046 * Handle the "add" command, which wants to add files to a new or
2047 * pre-existing archive.
2048 */
2049int doAdd(Bundle* bundle)
2050{
2051 ZipFile* zip = NULL;
2052 status_t result = UNKNOWN_ERROR;
2053 const char* zipFileName;
2054
2055 if (bundle->getUpdate()) {
2056 /* avoid confusion */
2057 fprintf(stderr, "ERROR: can't use '-u' with add\n");
2058 goto bail;
2059 }
2060
2061 if (bundle->getFileSpecCount() < 1) {
2062 fprintf(stderr, "ERROR: must specify zip file name\n");
2063 goto bail;
2064 }
2065 zipFileName = bundle->getFileSpecEntry(0);
2066
2067 if (bundle->getFileSpecCount() < 2) {
2068 fprintf(stderr, "NOTE: nothing to do\n");
2069 goto bail;
2070 }
2071
2072 zip = openReadWrite(zipFileName, true);
2073 if (zip == NULL) {
2074 fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
2075 goto bail;
2076 }
2077
2078 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2079 const char* fileName = bundle->getFileSpecEntry(i);
2080
2081 if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
2082 printf(" '%s'... (from gzip)\n", fileName);
2083 result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
2084 } else {
2085 if (bundle->getJunkPath()) {
2086 String8 storageName = String8(fileName).getPathLeaf();
Maurice Chu2675f762013-10-22 17:33:11 -07002087 printf(" '%s' as '%s'...\n", fileName,
2088 ResTable::normalizeForOutput(storageName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002089 result = zip->add(fileName, storageName.string(),
2090 bundle->getCompressionMethod(), NULL);
2091 } else {
2092 printf(" '%s'...\n", fileName);
2093 result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
2094 }
2095 }
2096 if (result != NO_ERROR) {
2097 fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
2098 if (result == NAME_NOT_FOUND) {
2099 fprintf(stderr, ": file not found\n");
2100 } else if (result == ALREADY_EXISTS) {
2101 fprintf(stderr, ": already exists in archive\n");
2102 } else {
2103 fprintf(stderr, "\n");
2104 }
2105 goto bail;
2106 }
2107 }
2108
2109 result = NO_ERROR;
2110
2111bail:
2112 delete zip;
2113 return (result != NO_ERROR);
2114}
2115
2116
2117/*
2118 * Delete files from an existing archive.
2119 */
2120int doRemove(Bundle* bundle)
2121{
2122 ZipFile* zip = NULL;
2123 status_t result = UNKNOWN_ERROR;
2124 const char* zipFileName;
2125
2126 if (bundle->getFileSpecCount() < 1) {
2127 fprintf(stderr, "ERROR: must specify zip file name\n");
2128 goto bail;
2129 }
2130 zipFileName = bundle->getFileSpecEntry(0);
2131
2132 if (bundle->getFileSpecCount() < 2) {
2133 fprintf(stderr, "NOTE: nothing to do\n");
2134 goto bail;
2135 }
2136
2137 zip = openReadWrite(zipFileName, false);
2138 if (zip == NULL) {
2139 fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
2140 zipFileName);
2141 goto bail;
2142 }
2143
2144 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2145 const char* fileName = bundle->getFileSpecEntry(i);
2146 ZipEntry* entry;
2147
2148 entry = zip->getEntryByName(fileName);
2149 if (entry == NULL) {
2150 printf(" '%s' NOT FOUND\n", fileName);
2151 continue;
2152 }
2153
2154 result = zip->remove(entry);
2155
2156 if (result != NO_ERROR) {
2157 fprintf(stderr, "Unable to delete '%s' from '%s'\n",
2158 bundle->getFileSpecEntry(i), zipFileName);
2159 goto bail;
2160 }
2161 }
2162
2163 /* update the archive */
2164 zip->flush();
2165
2166bail:
2167 delete zip;
2168 return (result != NO_ERROR);
2169}
2170
Adam Lesinski3921e872014-05-13 10:56:25 -07002171static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder, bool ignoreConfig=false) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002172 const size_t numDirs = dir->getDirs().size();
2173 for (size_t i = 0; i < numDirs; i++) {
Adam Lesinski3921e872014-05-13 10:56:25 -07002174 bool ignore = ignoreConfig;
2175 const sp<AaptDir>& subDir = dir->getDirs().valueAt(i);
2176 const char* dirStr = subDir->getLeaf().string();
2177 if (!ignore && strstr(dirStr, "mipmap") == dirStr) {
2178 ignore = true;
2179 }
2180 status_t err = addResourcesToBuilder(subDir, builder, ignore);
Adam Lesinskifab50872014-04-16 14:40:42 -07002181 if (err != NO_ERROR) {
2182 return err;
2183 }
2184 }
2185
2186 const size_t numFiles = dir->getFiles().size();
2187 for (size_t i = 0; i < numFiles; i++) {
2188 sp<AaptGroup> gp = dir->getFiles().valueAt(i);
2189 const size_t numConfigs = gp->getFiles().size();
2190 for (size_t j = 0; j < numConfigs; j++) {
Adam Lesinski3921e872014-05-13 10:56:25 -07002191 status_t err = NO_ERROR;
2192 if (ignoreConfig) {
2193 err = builder->getBaseSplit()->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2194 } else {
2195 err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2196 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002197 if (err != NO_ERROR) {
2198 fprintf(stderr, "Failed to add %s (%s) to builder.\n",
2199 gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string());
2200 return err;
2201 }
2202 }
2203 }
2204 return NO_ERROR;
2205}
2206
2207static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) {
2208 if (split->isBase()) {
2209 return original;
2210 }
2211
2212 String8 ext(original.getPathExtension());
2213 if (ext == String8(".apk")) {
2214 return String8::format("%s_%s%s",
2215 original.getBasePath().string(),
2216 split->getDirectorySafeName().string(),
2217 ext.string());
2218 }
2219
2220 return String8::format("%s_%s", original.string(),
2221 split->getDirectorySafeName().string());
2222}
Adam Lesinski282e1812014-01-23 18:17:42 -08002223
2224/*
2225 * Package up an asset directory and associated application files.
2226 */
2227int doPackage(Bundle* bundle)
2228{
2229 const char* outputAPKFile;
2230 int retVal = 1;
2231 status_t err;
2232 sp<AaptAssets> assets;
2233 int N;
2234 FILE* fp;
2235 String8 dependencyFile;
Adam Lesinskifab50872014-04-16 14:40:42 -07002236 sp<ApkBuilder> builder;
Adam Lesinski282e1812014-01-23 18:17:42 -08002237
Anton Krumina2ef5c02014-03-12 14:46:44 -07002238 // -c en_XA or/and ar_XB means do pseudolocalization
Adam Lesinskifab50872014-04-16 14:40:42 -07002239 sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
2240 err = configFilter->parse(bundle->getConfigurations());
Adam Lesinski282e1812014-01-23 18:17:42 -08002241 if (err != NO_ERROR) {
2242 goto bail;
2243 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002244 if (configFilter->containsPseudo()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002245 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
2246 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002247 if (configFilter->containsPseudoBidi()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002248 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
Adam Lesinski282e1812014-01-23 18:17:42 -08002249 }
2250
2251 N = bundle->getFileSpecCount();
2252 if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
Adam Lesinski09384302014-01-22 16:07:42 -08002253 && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002254 fprintf(stderr, "ERROR: no input files\n");
2255 goto bail;
2256 }
2257
2258 outputAPKFile = bundle->getOutputAPKFile();
2259
2260 // Make sure the filenames provided exist and are of the appropriate type.
2261 if (outputAPKFile) {
2262 FileType type;
2263 type = getFileType(outputAPKFile);
2264 if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
2265 fprintf(stderr,
2266 "ERROR: output file '%s' exists but is not regular file\n",
2267 outputAPKFile);
2268 goto bail;
2269 }
2270 }
2271
2272 // Load the assets.
2273 assets = new AaptAssets();
2274
2275 // Set up the resource gathering in assets if we're going to generate
2276 // dependency files. Every time we encounter a resource while slurping
2277 // the tree, we'll add it to these stores so we have full resource paths
2278 // to write to a dependency file.
2279 if (bundle->getGenDependencies()) {
2280 sp<FilePathStore> resPathStore = new FilePathStore;
2281 assets->setFullResPaths(resPathStore);
2282 sp<FilePathStore> assetPathStore = new FilePathStore;
2283 assets->setFullAssetPaths(assetPathStore);
2284 }
2285
2286 err = assets->slurpFromArgs(bundle);
2287 if (err < 0) {
2288 goto bail;
2289 }
2290
2291 if (bundle->getVerbose()) {
2292 assets->print(String8());
2293 }
2294
Adam Lesinskifab50872014-04-16 14:40:42 -07002295 // Create the ApkBuilder, which will collect the compiled files
2296 // to write to the final APK (or sets of APKs if we are building
2297 // a Split APK.
2298 builder = new ApkBuilder(configFilter);
2299
2300 // If we are generating a Split APK, find out which configurations to split on.
2301 if (bundle->getSplitConfigurations().size() > 0) {
2302 const Vector<String8>& splitStrs = bundle->getSplitConfigurations();
2303 const size_t numSplits = splitStrs.size();
2304 for (size_t i = 0; i < numSplits; i++) {
2305 std::set<ConfigDescription> configs;
2306 if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {
2307 fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());
2308 goto bail;
2309 }
2310
2311 err = builder->createSplitForConfigs(configs);
2312 if (err != NO_ERROR) {
2313 goto bail;
2314 }
2315 }
2316 }
2317
Adam Lesinski282e1812014-01-23 18:17:42 -08002318 // If they asked for any fileAs that need to be compiled, do so.
2319 if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002320 err = buildResources(bundle, assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002321 if (err != 0) {
2322 goto bail;
2323 }
2324 }
2325
2326 // At this point we've read everything and processed everything. From here
2327 // on out it's just writing output files.
2328 if (SourcePos::hasErrors()) {
2329 goto bail;
2330 }
2331
2332 // Update symbols with information about which ones are needed as Java symbols.
2333 assets->applyJavaSymbols();
2334 if (SourcePos::hasErrors()) {
2335 goto bail;
2336 }
2337
2338 // If we've been asked to generate a dependency file, do that here
2339 if (bundle->getGenDependencies()) {
2340 // If this is the packaging step, generate the dependency file next to
2341 // the output apk (e.g. bin/resources.ap_.d)
2342 if (outputAPKFile) {
2343 dependencyFile = String8(outputAPKFile);
2344 // Add the .d extension to the dependency file.
2345 dependencyFile.append(".d");
2346 } else {
2347 // Else if this is the R.java dependency generation step,
2348 // generate the dependency file in the R.java package subdirectory
2349 // e.g. gen/com/foo/app/R.java.d
2350 dependencyFile = String8(bundle->getRClassDir());
2351 dependencyFile.appendPath("R.java.d");
2352 }
2353 // Make sure we have a clean dependency file to start with
2354 fp = fopen(dependencyFile, "w");
2355 fclose(fp);
2356 }
2357
2358 // Write out R.java constants
2359 if (!assets->havePrivateSymbols()) {
2360 if (bundle->getCustomPackage() == NULL) {
2361 // Write the R.java file into the appropriate class directory
2362 // e.g. gen/com/foo/app/R.java
2363 err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
2364 } else {
2365 const String8 customPkg(bundle->getCustomPackage());
2366 err = writeResourceSymbols(bundle, assets, customPkg, true);
2367 }
2368 if (err < 0) {
2369 goto bail;
2370 }
2371 // If we have library files, we're going to write our R.java file into
2372 // the appropriate class directory for those libraries as well.
2373 // e.g. gen/com/foo/app/lib/R.java
2374 if (bundle->getExtraPackages() != NULL) {
2375 // Split on colon
2376 String8 libs(bundle->getExtraPackages());
2377 char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
2378 while (packageString != NULL) {
2379 // Write the R.java file out with the correct package name
2380 err = writeResourceSymbols(bundle, assets, String8(packageString), true);
2381 if (err < 0) {
2382 goto bail;
2383 }
2384 packageString = strtok(NULL, ":");
2385 }
2386 libs.unlockBuffer();
2387 }
2388 } else {
2389 err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
2390 if (err < 0) {
2391 goto bail;
2392 }
2393 err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
2394 if (err < 0) {
2395 goto bail;
2396 }
2397 }
2398
2399 // Write out the ProGuard file
2400 err = writeProguardFile(bundle, assets);
2401 if (err < 0) {
2402 goto bail;
2403 }
2404
2405 // Write the apk
2406 if (outputAPKFile) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002407 // Gather all resources and add them to the APK Builder. The builder will then
2408 // figure out which Split they belong in.
2409 err = addResourcesToBuilder(assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002410 if (err != NO_ERROR) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002411 goto bail;
2412 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002413
2414 const Vector<sp<ApkSplit> >& splits = builder->getSplits();
2415 const size_t numSplits = splits.size();
2416 for (size_t i = 0; i < numSplits; i++) {
2417 const sp<ApkSplit>& split = splits[i];
2418 String8 outputPath = buildApkName(String8(outputAPKFile), split);
2419 err = writeAPK(bundle, outputPath, split);
2420 if (err != NO_ERROR) {
2421 fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
2422 goto bail;
2423 }
2424 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002425 }
2426
2427 // If we've been asked to generate a dependency file, we need to finish up here.
2428 // the writeResourceSymbols and writeAPK functions have already written the target
2429 // half of the dependency file, now we need to write the prerequisites. (files that
2430 // the R.java file or .ap_ file depend on)
2431 if (bundle->getGenDependencies()) {
2432 // Now that writeResourceSymbols or writeAPK has taken care of writing
2433 // the targets to our dependency file, we'll write the prereqs
2434 fp = fopen(dependencyFile, "a+");
2435 fprintf(fp, " : ");
2436 bool includeRaw = (outputAPKFile != NULL);
2437 err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
2438 // Also manually add the AndroidManifeset since it's not under res/ or assets/
2439 // and therefore was not added to our pathstores during slurping
2440 fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
2441 fclose(fp);
2442 }
2443
2444 retVal = 0;
2445bail:
2446 if (SourcePos::hasErrors()) {
2447 SourcePos::printErrors(stderr);
2448 }
2449 return retVal;
2450}
2451
2452/*
2453 * Do PNG Crunching
2454 * PRECONDITIONS
2455 * -S flag points to a source directory containing drawable* folders
2456 * -C flag points to destination directory. The folder structure in the
2457 * source directory will be mirrored to the destination (cache) directory
2458 *
2459 * POSTCONDITIONS
2460 * Destination directory will be updated to match the PNG files in
Maurice Chu2675f762013-10-22 17:33:11 -07002461 * the source directory.
Adam Lesinski282e1812014-01-23 18:17:42 -08002462 */
2463int doCrunch(Bundle* bundle)
2464{
2465 fprintf(stdout, "Crunching PNG Files in ");
2466 fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
2467 fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
2468
2469 updatePreProcessedCache(bundle);
2470
2471 return NO_ERROR;
2472}
2473
2474/*
2475 * Do PNG Crunching on a single flag
2476 * -i points to a single png file
2477 * -o points to a single png output file
2478 */
2479int doSingleCrunch(Bundle* bundle)
2480{
2481 fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile());
2482 fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile());
2483
2484 String8 input(bundle->getSingleCrunchInputFile());
2485 String8 output(bundle->getSingleCrunchOutputFile());
2486
2487 if (preProcessImageToCache(bundle, input, output) != NO_ERROR) {
2488 // we can't return the status_t as it gets truncate to the lower 8 bits.
2489 return 42;
2490 }
2491
2492 return NO_ERROR;
2493}
2494
2495char CONSOLE_DATA[2925] = {
2496 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2497 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2498 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2499 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2500 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63,
2501 86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83,
2502 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2503 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2504 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81,
2505 81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32,
2506 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2507 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2508 32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59,
2509 59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2510 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2511 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59,
2512 59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32,
2513 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2514 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2515 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87,
2516 58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32,
2517 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2518 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2519 47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58,
2520 121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2521 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2522 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81,
2523 81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32,
2524 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2525 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2526 32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59,
2527 59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2528 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2529 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59,
2530 59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32,
2531 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2532 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2533 32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81,
2534 70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32,
2535 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2536 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2537 32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81,
2538 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2539 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2540 32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109,
2541 81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32,
2542 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2543 32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59,
2544 59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59,
2545 60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2546 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59,
2547 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46,
2548 61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32,
2549 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2550 32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2551 59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81,
2552 109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32,
2553 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81,
2554 67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2555 59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58,
2556 61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32,
2557 32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59,
2558 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37,
2559 73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2560 59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2561 32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59,
2562 59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96,
2563 46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61,
2564 97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32,
2565 46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61,
2566 119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59,
2567 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32,
2568 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2569 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119,
2570 119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2571 59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10,
2572 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2573 32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81,
2574 81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41,
2575 87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2576 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81,
2577 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58,
2578 45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32,
2579 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2580 32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2581 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32,
2582 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2583 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58,
2584 59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58,
2585 59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2586 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2587 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81,
2588 81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32,
2589 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2590 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2591 32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2592 81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2593 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2594 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106,
2595 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59,
2596 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2597 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2598 32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2599 81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32,
2600 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2601 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2602 58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59,
2603 61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2604 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2605 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81,
2606 81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32,
2607 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2608 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2609 32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2610 81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2611 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2612 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59,
2613 59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59,
2614 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2615 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2616 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33,
2617 58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32,
2618 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2619 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2620 61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59,
2621 59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32,
2622 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32,
2623 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95,
2624 32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59,
2625 61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2626 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2627 32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45,
2628 59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32,
2629 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32,
2630 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59,
2631 59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59,
2632 59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2633 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2634 32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2635 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2636 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2637 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2638 46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61,
2639 46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2640 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2641 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59,
2642 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59,
2643 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2644 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2645 32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32,
2646 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32,
2647 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2648 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61,
2649 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59,
2650 59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2651 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2652 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32,
2653 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32,
2654 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2655 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2656 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2657 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2658 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10
2659 };