blob: afeb5460b25fff83bbf5f20a8f2fc0dc98e4a13c [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 {
Adam Lesinskid7a94da2014-07-25 14:38:54 -0700614 FeatureGroup() : openGLESVersion(-1) {}
615
Adam Lesinski2c72b682014-06-24 09:56:01 -0700616 /**
617 * Human readable label
618 */
619 String8 label;
620
621 /**
622 * Explicit features defined in the group
623 */
624 KeyedVector<String8, bool> features;
Adam Lesinskid7a94da2014-07-25 14:38:54 -0700625
626 /**
627 * OpenGL ES version required
628 */
629 int openGLESVersion;
Adam Lesinski2c72b682014-06-24 09:56:01 -0700630};
631
632static void addImpliedFeature(KeyedVector<String8, ImpliedFeature>* impliedFeatures,
633 const char* name, const char* reason) {
634 String8 name8(name);
635 ssize_t idx = impliedFeatures->indexOfKey(name8);
636 if (idx < 0) {
637 idx = impliedFeatures->add(name8, ImpliedFeature());
638 impliedFeatures->editValueAt(idx).name = name8;
639 }
640 impliedFeatures->editValueAt(idx).reasons.add(String8(reason));
641}
642
643static void printFeatureGroup(const FeatureGroup& grp,
644 const KeyedVector<String8, ImpliedFeature>* impliedFeatures = NULL) {
645 printf("feature-group: label='%s'\n", grp.label.string());
646
Adam Lesinskid7a94da2014-07-25 14:38:54 -0700647 if (grp.openGLESVersion > 0) {
648 printf(" uses-gl-es: '0x%x'\n", grp.openGLESVersion);
649 }
650
Adam Lesinski2c72b682014-06-24 09:56:01 -0700651 const size_t numFeatures = grp.features.size();
652 for (size_t i = 0; i < numFeatures; i++) {
653 if (!grp.features[i]) {
654 continue;
655 }
656
657 const String8& featureName = grp.features.keyAt(i);
658 printf(" uses-feature: name='%s'\n",
659 ResTable::normalizeForOutput(featureName.string()).string());
660 }
661
662 const size_t numImpliedFeatures =
663 (impliedFeatures != NULL) ? impliedFeatures->size() : 0;
664 for (size_t i = 0; i < numImpliedFeatures; i++) {
665 const ImpliedFeature& impliedFeature = impliedFeatures->valueAt(i);
666 if (grp.features.indexOfKey(impliedFeature.name) >= 0) {
667 // The feature is explicitly set, no need to use implied
668 // definition.
669 continue;
670 }
671
672 String8 printableFeatureName(ResTable::normalizeForOutput(
673 impliedFeature.name.string()));
674 printf(" uses-feature: name='%s'\n", printableFeatureName.string());
675 printf(" uses-implied-feature: name='%s' reason='",
676 printableFeatureName.string());
677 const size_t numReasons = impliedFeature.reasons.size();
678 for (size_t j = 0; j < numReasons; j++) {
679 printf("%s", impliedFeature.reasons[j].string());
680 if (j + 2 < numReasons) {
681 printf(", ");
682 } else if (j + 1 < numReasons) {
683 printf(", and ");
684 }
685 }
686 printf("'\n");
687 }
688}
689
690static void addParentFeatures(FeatureGroup* grp, const String8& name) {
691 if (name == "android.hardware.camera.autofocus" ||
692 name == "android.hardware.camera.flash") {
693 grp->features.add(String8("android.hardware.camera"), true);
694 } else if (name == "android.hardware.location.gps" ||
695 name == "android.hardware.location.network") {
696 grp->features.add(String8("android.hardware.location"), true);
697 } else if (name == "android.hardware.touchscreen.multitouch") {
698 grp->features.add(String8("android.hardware.touchscreen"), true);
699 } else if (name == "android.hardware.touchscreen.multitouch.distinct") {
700 grp->features.add(String8("android.hardware.touchscreen.multitouch"), true);
701 grp->features.add(String8("android.hardware.touchscreen"), true);
Adam Lesinskid7a94da2014-07-25 14:38:54 -0700702 } else if (name == "android.hardware.opengles.aep") {
703 const int openGLESVersion31 = 0x00030001;
704 if (openGLESVersion31 > grp->openGLESVersion) {
705 grp->openGLESVersion = openGLESVersion31;
706 }
Adam Lesinski2c72b682014-06-24 09:56:01 -0700707 }
708}
709
Adam Lesinski282e1812014-01-23 18:17:42 -0800710/*
711 * Handle the "dump" command, to extract select data from an archive.
712 */
713extern char CONSOLE_DATA[2925]; // see EOF
714int doDump(Bundle* bundle)
715{
716 status_t result = UNKNOWN_ERROR;
717 Asset* asset = NULL;
718
719 if (bundle->getFileSpecCount() < 1) {
720 fprintf(stderr, "ERROR: no dump option specified\n");
721 return 1;
722 }
723
724 if (bundle->getFileSpecCount() < 2) {
725 fprintf(stderr, "ERROR: no dump file specified\n");
726 return 1;
727 }
728
729 const char* option = bundle->getFileSpecEntry(0);
730 const char* filename = bundle->getFileSpecEntry(1);
731
732 AssetManager assets;
Narayan Kamathf85e41f2014-01-24 14:14:30 +0000733 int32_t assetsCookie;
Adam Lesinski282e1812014-01-23 18:17:42 -0800734 if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
735 fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
736 return 1;
737 }
738
739 // Make a dummy config for retrieving resources... we need to supply
740 // non-default values for some configs so that we can retrieve resources
741 // in the app that don't have a default. The most important of these is
742 // the API version because key resources like icons will have an implicit
743 // version if they are using newer config types like density.
744 ResTable_config config;
Narayan Kamath91447d82014-01-21 15:32:36 +0000745 memset(&config, 0, sizeof(ResTable_config));
Adam Lesinski282e1812014-01-23 18:17:42 -0800746 config.language[0] = 'e';
747 config.language[1] = 'n';
748 config.country[0] = 'U';
749 config.country[1] = 'S';
750 config.orientation = ResTable_config::ORIENTATION_PORT;
751 config.density = ResTable_config::DENSITY_MEDIUM;
752 config.sdkVersion = 10000; // Very high.
753 config.screenWidthDp = 320;
754 config.screenHeightDp = 480;
755 config.smallestScreenWidthDp = 320;
756 assets.setConfiguration(config);
757
758 const ResTable& res = assets.getResources(false);
759 if (&res == NULL) {
760 fprintf(stderr, "ERROR: dump failed because no resource table was found\n");
761 goto bail;
Adam Lesinski25e9d552014-05-19 15:01:43 -0700762 } else if (res.getError() != NO_ERROR) {
763 fprintf(stderr, "ERROR: dump failed because the resource table is invalid/corrupt.\n");
764 goto bail;
Adam Lesinski282e1812014-01-23 18:17:42 -0800765 }
766
767 if (strcmp("resources", option) == 0) {
768#ifndef HAVE_ANDROID_OS
769 res.print(bundle->getValues());
770#endif
771
772 } else if (strcmp("strings", option) == 0) {
773 const ResStringPool* pool = res.getTableStringBlock(0);
774 printStringPool(pool);
775
776 } else if (strcmp("xmltree", option) == 0) {
777 if (bundle->getFileSpecCount() < 3) {
778 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
779 goto bail;
780 }
781
782 for (int i=2; i<bundle->getFileSpecCount(); i++) {
783 const char* resname = bundle->getFileSpecEntry(i);
784 ResXMLTree tree;
785 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
786 if (asset == NULL) {
787 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
788 goto bail;
789 }
790
791 if (tree.setTo(asset->getBuffer(true),
792 asset->getLength()) != NO_ERROR) {
793 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
794 goto bail;
795 }
796 tree.restart();
797 printXMLBlock(&tree);
798 tree.uninit();
799 delete asset;
800 asset = NULL;
801 }
802
803 } else if (strcmp("xmlstrings", option) == 0) {
804 if (bundle->getFileSpecCount() < 3) {
805 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
806 goto bail;
807 }
808
809 for (int i=2; i<bundle->getFileSpecCount(); i++) {
810 const char* resname = bundle->getFileSpecEntry(i);
811 ResXMLTree tree;
812 asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
813 if (asset == NULL) {
814 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
815 goto bail;
816 }
817
818 if (tree.setTo(asset->getBuffer(true),
819 asset->getLength()) != NO_ERROR) {
820 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
821 goto bail;
822 }
823 printStringPool(&tree.getStrings());
824 delete asset;
825 asset = NULL;
826 }
827
828 } else {
829 ResXMLTree tree;
830 asset = assets.openNonAsset("AndroidManifest.xml",
831 Asset::ACCESS_BUFFER);
832 if (asset == NULL) {
833 fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
834 goto bail;
835 }
836
837 if (tree.setTo(asset->getBuffer(true),
838 asset->getLength()) != NO_ERROR) {
839 fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
840 goto bail;
841 }
842 tree.restart();
843
844 if (strcmp("permissions", option) == 0) {
845 size_t len;
846 ResXMLTree::event_code_t code;
847 int depth = 0;
848 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
849 if (code == ResXMLTree::END_TAG) {
850 depth--;
851 continue;
852 }
853 if (code != ResXMLTree::START_TAG) {
854 continue;
855 }
856 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700857 const char16_t* ctag16 = tree.getElementName(&len);
858 if (ctag16 == NULL) {
859 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
860 goto bail;
861 }
862 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -0800863 //printf("Depth %d tag %s\n", depth, tag.string());
864 if (depth == 1) {
865 if (tag != "manifest") {
866 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
867 goto bail;
868 }
869 String8 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -0700870 printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800871 } else if (depth == 2 && tag == "permission") {
872 String8 error;
873 String8 name = getAttribute(tree, NAME_ATTR, &error);
874 if (error != "") {
875 fprintf(stderr, "ERROR: %s\n", error.string());
876 goto bail;
877 }
Maurice Chu2675f762013-10-22 17:33:11 -0700878 printf("permission: %s\n",
879 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800880 } else if (depth == 2 && tag == "uses-permission") {
881 String8 error;
882 String8 name = getAttribute(tree, NAME_ATTR, &error);
883 if (error != "") {
884 fprintf(stderr, "ERROR: %s\n", error.string());
885 goto bail;
886 }
Adam Lesinski58f1f362013-11-12 12:59:08 -0800887 printUsesPermission(name,
888 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
889 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
Adam Lesinski282e1812014-01-23 18:17:42 -0800890 }
891 }
892 } else if (strcmp("badging", option) == 0) {
893 Vector<String8> locales;
894 res.getLocales(&locales);
895
896 Vector<ResTable_config> configs;
897 res.getConfigurations(&configs);
898 SortedVector<int> densities;
899 const size_t NC = configs.size();
900 for (size_t i=0; i<NC; i++) {
901 int dens = configs[i].density;
902 if (dens == 0) {
903 dens = 160;
904 }
905 densities.add(dens);
906 }
907
908 size_t len;
909 ResXMLTree::event_code_t code;
910 int depth = 0;
911 String8 error;
912 bool withinActivity = false;
913 bool isMainActivity = false;
914 bool isLauncherActivity = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800915 bool isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800916 bool isSearchable = false;
917 bool withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700918 bool withinSupportsInput = false;
Adam Lesinski2c72b682014-06-24 09:56:01 -0700919 bool withinFeatureGroup = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800920 bool withinReceiver = false;
921 bool withinService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700922 bool withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800923 bool withinIntentFilter = false;
924 bool hasMainActivity = false;
925 bool hasOtherActivities = false;
926 bool hasOtherReceivers = false;
927 bool hasOtherServices = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700928 bool hasIntentFilter = false;
929
Adam Lesinski282e1812014-01-23 18:17:42 -0800930 bool hasWallpaperService = false;
931 bool hasImeService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700932 bool hasAccessibilityService = false;
933 bool hasPrintService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800934 bool hasWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700935 bool hasDeviceAdminReceiver = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700936 bool hasPaymentService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700937 bool hasDocumentsProvider = false;
938 bool hasCameraActivity = false;
939 bool hasCameraSecureActivity = false;
940 bool hasLauncher = false;
941 bool hasNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400942 bool hasDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700943
Adam Lesinski282e1812014-01-23 18:17:42 -0800944 bool actMainActivity = false;
945 bool actWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700946 bool actDeviceAdminEnabled = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800947 bool actImeService = false;
948 bool actWallpaperService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700949 bool actAccessibilityService = false;
950 bool actPrintService = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700951 bool actHostApduService = false;
952 bool actOffHostApduService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700953 bool actDocumentsProvider = false;
954 bool actNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400955 bool actDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700956 bool actCamera = false;
957 bool actCameraSecure = false;
958 bool catLauncher = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700959 bool hasMetaHostPaymentCategory = false;
960 bool hasMetaOffHostPaymentCategory = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700961
962 // These permissions are required by services implementing services
963 // the system binds to (IME, Accessibility, PrintServices, etc.)
964 bool hasBindDeviceAdminPermission = false;
965 bool hasBindInputMethodPermission = false;
966 bool hasBindAccessibilityServicePermission = false;
967 bool hasBindPrintServicePermission = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700968 bool hasBindNfcServicePermission = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700969 bool hasRequiredSafAttributes = false;
970 bool hasBindNotificationListenerServicePermission = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400971 bool hasBindDreamServicePermission = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800972
973 // These two implement the implicit permissions that are granted
974 // to pre-1.6 applications.
975 bool hasWriteExternalStoragePermission = false;
976 bool hasReadPhoneStatePermission = false;
977
978 // If an app requests write storage, they will also get read storage.
979 bool hasReadExternalStoragePermission = false;
980
981 // Implement transition to read and write call log.
982 bool hasReadContactsPermission = false;
983 bool hasWriteContactsPermission = false;
984 bool hasReadCallLogPermission = false;
985 bool hasWriteCallLogPermission = false;
986
987 // This next group of variables is used to implement a group of
988 // backward-compatibility heuristics necessitated by the addition of
989 // some new uses-feature constants in 2.1 and 2.2. In most cases, the
990 // heuristic is "if an app requests a permission but doesn't explicitly
991 // request the corresponding <uses-feature>, presume it's there anyway".
Adam Lesinski2c72b682014-06-24 09:56:01 -0700992
Adam Lesinski282e1812014-01-23 18:17:42 -0800993 // 2.2 also added some other features that apps can request, but that
994 // have no corresponding permission, so we cannot implement any
995 // back-compatibility heuristic for them. The below are thus unnecessary
996 // (but are retained here for documentary purposes.)
997 //bool specCompassFeature = false;
998 //bool specAccelerometerFeature = false;
999 //bool specProximityFeature = false;
1000 //bool specAmbientLightFeature = false;
1001 //bool specLiveWallpaperFeature = false;
1002
1003 int targetSdk = 0;
1004 int smallScreen = 1;
1005 int normalScreen = 1;
1006 int largeScreen = 1;
1007 int xlargeScreen = 1;
1008 int anyDensity = 1;
1009 int requiresSmallestWidthDp = 0;
1010 int compatibleWidthLimitDp = 0;
1011 int largestWidthLimitDp = 0;
1012 String8 pkg;
1013 String8 activityName;
1014 String8 activityLabel;
1015 String8 activityIcon;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001016 String8 activityBanner;
Adam Lesinski282e1812014-01-23 18:17:42 -08001017 String8 receiverName;
1018 String8 serviceName;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001019 Vector<String8> supportedInput;
Adam Lesinski2c72b682014-06-24 09:56:01 -07001020
1021 FeatureGroup commonFeatures;
1022 Vector<FeatureGroup> featureGroups;
1023 KeyedVector<String8, ImpliedFeature> impliedFeatures;
1024
Adam Lesinski282e1812014-01-23 18:17:42 -08001025 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1026 if (code == ResXMLTree::END_TAG) {
1027 depth--;
1028 if (depth < 2) {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001029 if (withinSupportsInput && !supportedInput.isEmpty()) {
1030 printf("supports-input: '");
1031 const size_t N = supportedInput.size();
1032 for (size_t i=0; i<N; i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07001033 printf("%s", ResTable::normalizeForOutput(
1034 supportedInput[i].string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001035 if (i != N - 1) {
1036 printf("' '");
1037 } else {
1038 printf("'\n");
1039 }
1040 }
1041 supportedInput.clear();
1042 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001043 withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001044 withinSupportsInput = false;
Adam Lesinski2c72b682014-06-24 09:56:01 -07001045 withinFeatureGroup = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001046 } else if (depth < 3) {
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001047 if (withinActivity && isMainActivity) {
Maurice Chu2675f762013-10-22 17:33:11 -07001048 String8 aName(getComponentName(pkg, activityName));
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001049 if (isLauncherActivity) {
1050 printf("launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001051 if (aName.length() > 0) {
1052 printf(" name='%s' ",
1053 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001054 }
1055 printf(" label='%s' icon='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001056 ResTable::normalizeForOutput(activityLabel.string()).string(),
1057 ResTable::normalizeForOutput(activityIcon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001058 }
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001059 if (isLeanbackLauncherActivity) {
1060 printf("leanback-launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001061 if (aName.length() > 0) {
1062 printf(" name='%s' ",
1063 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001064 }
1065 printf(" label='%s' icon='%s' banner='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001066 ResTable::normalizeForOutput(activityLabel.string()).string(),
1067 ResTable::normalizeForOutput(activityIcon.string()).string(),
1068 ResTable::normalizeForOutput(activityBanner.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001069 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001070 }
1071 if (!hasIntentFilter) {
1072 hasOtherActivities |= withinActivity;
1073 hasOtherReceivers |= withinReceiver;
1074 hasOtherServices |= withinService;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001075 } else {
1076 if (withinService) {
1077 hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
1078 hasBindNfcServicePermission);
1079 hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
1080 hasBindNfcServicePermission);
1081 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001082 }
1083 withinActivity = false;
1084 withinService = false;
1085 withinReceiver = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001086 withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001087 hasIntentFilter = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001088 isMainActivity = isLauncherActivity = isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001089 } else if (depth < 4) {
1090 if (withinIntentFilter) {
1091 if (withinActivity) {
1092 hasMainActivity |= actMainActivity;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001093 hasLauncher |= catLauncher;
1094 hasCameraActivity |= actCamera;
1095 hasCameraSecureActivity |= actCameraSecure;
1096 hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure;
Adam Lesinski282e1812014-01-23 18:17:42 -08001097 } else if (withinReceiver) {
1098 hasWidgetReceivers |= actWidgetReceivers;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001099 hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
1100 hasBindDeviceAdminPermission);
1101 hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
Adam Lesinski282e1812014-01-23 18:17:42 -08001102 } else if (withinService) {
1103 hasImeService |= actImeService;
1104 hasWallpaperService |= actWallpaperService;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001105 hasAccessibilityService |= (actAccessibilityService &&
1106 hasBindAccessibilityServicePermission);
1107 hasPrintService |= (actPrintService && hasBindPrintServicePermission);
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001108 hasNotificationListenerService |= actNotificationListenerService &&
1109 hasBindNotificationListenerServicePermission;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001110 hasDreamService |= actDreamService && hasBindDreamServicePermission;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001111 hasOtherServices |= (!actImeService && !actWallpaperService &&
Adam Lesinski94fc9122013-09-30 17:16:09 -07001112 !actAccessibilityService && !actPrintService &&
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001113 !actHostApduService && !actOffHostApduService &&
1114 !actNotificationListenerService);
1115 } else if (withinProvider) {
1116 hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes;
Adam Lesinski282e1812014-01-23 18:17:42 -08001117 }
1118 }
1119 withinIntentFilter = false;
1120 }
1121 continue;
1122 }
1123 if (code != ResXMLTree::START_TAG) {
1124 continue;
1125 }
1126 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001127
1128 const char16_t* ctag16 = tree.getElementName(&len);
1129 if (ctag16 == NULL) {
1130 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
1131 goto bail;
1132 }
1133 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -08001134 //printf("Depth %d, %s\n", depth, tag.string());
1135 if (depth == 1) {
1136 if (tag != "manifest") {
1137 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
1138 goto bail;
1139 }
1140 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -07001141 printf("package: name='%s' ",
1142 ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001143 int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
1144 if (error != "") {
1145 fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
1146 goto bail;
1147 }
1148 if (versionCode > 0) {
1149 printf("versionCode='%d' ", versionCode);
1150 } else {
1151 printf("versionCode='' ");
1152 }
1153 String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
1154 if (error != "") {
1155 fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
1156 goto bail;
1157 }
Maurice Chu2675f762013-10-22 17:33:11 -07001158 printf("versionName='%s'\n",
1159 ResTable::normalizeForOutput(versionName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001160 } else if (depth == 2) {
1161 withinApplication = false;
1162 if (tag == "application") {
1163 withinApplication = true;
1164
1165 String8 label;
1166 const size_t NL = locales.size();
1167 for (size_t i=0; i<NL; i++) {
1168 const char* localeStr = locales[i].string();
1169 assets.setLocale(localeStr != NULL ? localeStr : "");
1170 String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1171 if (llabel != "") {
1172 if (localeStr == NULL || strlen(localeStr) == 0) {
1173 label = llabel;
Maurice Chu2675f762013-10-22 17:33:11 -07001174 printf("application-label:'%s'\n",
1175 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001176 } else {
1177 if (label == "") {
1178 label = llabel;
1179 }
1180 printf("application-label-%s:'%s'\n", localeStr,
Maurice Chu2675f762013-10-22 17:33:11 -07001181 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001182 }
1183 }
1184 }
1185
1186 ResTable_config tmpConfig = config;
1187 const size_t ND = densities.size();
1188 for (size_t i=0; i<ND; i++) {
1189 tmpConfig.density = densities[i];
1190 assets.setConfiguration(tmpConfig);
1191 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1192 if (icon != "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001193 printf("application-icon-%d:'%s'\n", densities[i],
1194 ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001195 }
1196 }
1197 assets.setConfiguration(config);
1198
1199 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1200 if (error != "") {
1201 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
1202 goto bail;
1203 }
1204 int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
1205 if (error != "") {
1206 fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
1207 goto bail;
1208 }
Maurice Chu2675f762013-10-22 17:33:11 -07001209 printf("application: label='%s' ",
1210 ResTable::normalizeForOutput(label.string()).string());
1211 printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001212 if (testOnly != 0) {
1213 printf("testOnly='%d'\n", testOnly);
1214 }
1215
1216 int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
1217 if (error != "") {
1218 fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
1219 goto bail;
1220 }
1221 if (debuggable != 0) {
1222 printf("application-debuggable\n");
1223 }
1224 } else if (tag == "uses-sdk") {
1225 int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
1226 if (error != "") {
1227 error = "";
1228 String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
1229 if (error != "") {
1230 fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
1231 error.string());
1232 goto bail;
1233 }
1234 if (name == "Donut") targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001235 printf("sdkVersion:'%s'\n",
1236 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001237 } else if (code != -1) {
1238 targetSdk = code;
1239 printf("sdkVersion:'%d'\n", code);
1240 }
1241 code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
1242 if (code != -1) {
1243 printf("maxSdkVersion:'%d'\n", code);
1244 }
1245 code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
1246 if (error != "") {
1247 error = "";
1248 String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
1249 if (error != "") {
1250 fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
1251 error.string());
1252 goto bail;
1253 }
1254 if (name == "Donut" && targetSdk < 4) targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001255 printf("targetSdkVersion:'%s'\n",
1256 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001257 } else if (code != -1) {
1258 if (targetSdk < code) {
1259 targetSdk = code;
1260 }
1261 printf("targetSdkVersion:'%d'\n", code);
1262 }
1263 } else if (tag == "uses-configuration") {
1264 int32_t reqTouchScreen = getIntegerAttribute(tree,
1265 REQ_TOUCH_SCREEN_ATTR, NULL, 0);
1266 int32_t reqKeyboardType = getIntegerAttribute(tree,
1267 REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
1268 int32_t reqHardKeyboard = getIntegerAttribute(tree,
1269 REQ_HARD_KEYBOARD_ATTR, NULL, 0);
1270 int32_t reqNavigation = getIntegerAttribute(tree,
1271 REQ_NAVIGATION_ATTR, NULL, 0);
1272 int32_t reqFiveWayNav = getIntegerAttribute(tree,
1273 REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
1274 printf("uses-configuration:");
1275 if (reqTouchScreen != 0) {
1276 printf(" reqTouchScreen='%d'", reqTouchScreen);
1277 }
1278 if (reqKeyboardType != 0) {
1279 printf(" reqKeyboardType='%d'", reqKeyboardType);
1280 }
1281 if (reqHardKeyboard != 0) {
1282 printf(" reqHardKeyboard='%d'", reqHardKeyboard);
1283 }
1284 if (reqNavigation != 0) {
1285 printf(" reqNavigation='%d'", reqNavigation);
1286 }
1287 if (reqFiveWayNav != 0) {
1288 printf(" reqFiveWayNav='%d'", reqFiveWayNav);
1289 }
1290 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001291 } else if (tag == "supports-input") {
1292 withinSupportsInput = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001293 } else if (tag == "supports-screens") {
1294 smallScreen = getIntegerAttribute(tree,
1295 SMALL_SCREEN_ATTR, NULL, 1);
1296 normalScreen = getIntegerAttribute(tree,
1297 NORMAL_SCREEN_ATTR, NULL, 1);
1298 largeScreen = getIntegerAttribute(tree,
1299 LARGE_SCREEN_ATTR, NULL, 1);
1300 xlargeScreen = getIntegerAttribute(tree,
1301 XLARGE_SCREEN_ATTR, NULL, 1);
1302 anyDensity = getIntegerAttribute(tree,
1303 ANY_DENSITY_ATTR, NULL, 1);
1304 requiresSmallestWidthDp = getIntegerAttribute(tree,
1305 REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
1306 compatibleWidthLimitDp = getIntegerAttribute(tree,
1307 COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1308 largestWidthLimitDp = getIntegerAttribute(tree,
1309 LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
Adam Lesinski2c72b682014-06-24 09:56:01 -07001310 } else if (tag == "feature-group") {
1311 withinFeatureGroup = true;
1312 FeatureGroup group;
1313 group.label = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1314 if (error != "") {
1315 fprintf(stderr, "ERROR getting 'android:label' attribute:"
1316 " %s\n", error.string());
1317 goto bail;
1318 }
1319 featureGroups.add(group);
1320
Adam Lesinski282e1812014-01-23 18:17:42 -08001321 } else if (tag == "uses-feature") {
1322 String8 name = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001323 if (name != "" && error == "") {
1324 int req = getIntegerAttribute(tree,
1325 REQUIRED_ATTR, NULL, 1);
1326
Adam Lesinski2c72b682014-06-24 09:56:01 -07001327 commonFeatures.features.add(name, req);
1328 if (req) {
1329 addParentFeatures(&commonFeatures, name);
Adam Lesinski282e1812014-01-23 18:17:42 -08001330 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001331 } else {
1332 int vers = getIntegerAttribute(tree,
1333 GL_ES_VERSION_ATTR, &error);
1334 if (error == "") {
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001335 if (vers > commonFeatures.openGLESVersion) {
1336 commonFeatures.openGLESVersion = vers;
1337 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001338 }
1339 }
1340 } else if (tag == "uses-permission") {
1341 String8 name = getAttribute(tree, NAME_ATTR, &error);
1342 if (name != "" && error == "") {
1343 if (name == "android.permission.CAMERA") {
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001344 addImpliedFeature(&impliedFeatures, "android.hardware.camera",
Adam Lesinski2c72b682014-06-24 09:56:01 -07001345 String8::format("requested %s permission", name.string())
1346 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001347 } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001348 addImpliedFeature(&impliedFeatures, "android.hardware.location.gps",
1349 String8::format("requested %s permission", name.string())
1350 .string());
1351 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1352 String8::format("requested %s permission", name.string())
1353 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001354 } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001355 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1356 String8::format("requested %s permission", name.string())
1357 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001358 } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001359 addImpliedFeature(&impliedFeatures, "android.hardware.location.network",
1360 String8::format("requested %s permission", name.string())
1361 .string());
1362 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1363 String8::format("requested %s permission", name.string())
1364 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001365 } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
1366 name == "android.permission.INSTALL_LOCATION_PROVIDER") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001367 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1368 String8::format("requested %s permission", name.string())
1369 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001370 } else if (name == "android.permission.BLUETOOTH" ||
1371 name == "android.permission.BLUETOOTH_ADMIN") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001372 if (targetSdk > 4) {
1373 addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
1374 String8::format("requested %s permission", name.string())
1375 .string());
1376 addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
1377 "targetSdkVersion > 4");
1378 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001379 } else if (name == "android.permission.RECORD_AUDIO") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001380 addImpliedFeature(&impliedFeatures, "android.hardware.microphone",
1381 String8::format("requested %s permission", name.string())
1382 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001383 } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
1384 name == "android.permission.CHANGE_WIFI_STATE" ||
1385 name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001386 addImpliedFeature(&impliedFeatures, "android.hardware.wifi",
1387 String8::format("requested %s permission", name.string())
1388 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001389 } else if (name == "android.permission.CALL_PHONE" ||
1390 name == "android.permission.CALL_PRIVILEGED" ||
1391 name == "android.permission.MODIFY_PHONE_STATE" ||
1392 name == "android.permission.PROCESS_OUTGOING_CALLS" ||
1393 name == "android.permission.READ_SMS" ||
1394 name == "android.permission.RECEIVE_SMS" ||
1395 name == "android.permission.RECEIVE_MMS" ||
1396 name == "android.permission.RECEIVE_WAP_PUSH" ||
1397 name == "android.permission.SEND_SMS" ||
1398 name == "android.permission.WRITE_APN_SETTINGS" ||
1399 name == "android.permission.WRITE_SMS") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001400 addImpliedFeature(&impliedFeatures, "android.hardware.telephony",
1401 String8("requested a telephony permission").string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001402 } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
1403 hasWriteExternalStoragePermission = true;
1404 } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
1405 hasReadExternalStoragePermission = true;
1406 } else if (name == "android.permission.READ_PHONE_STATE") {
1407 hasReadPhoneStatePermission = true;
1408 } else if (name == "android.permission.READ_CONTACTS") {
1409 hasReadContactsPermission = true;
1410 } else if (name == "android.permission.WRITE_CONTACTS") {
1411 hasWriteContactsPermission = true;
1412 } else if (name == "android.permission.READ_CALL_LOG") {
1413 hasReadCallLogPermission = true;
1414 } else if (name == "android.permission.WRITE_CALL_LOG") {
1415 hasWriteCallLogPermission = true;
1416 }
Adam Lesinski58f1f362013-11-12 12:59:08 -08001417
1418 printUsesPermission(name,
1419 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
1420 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
1421 } else {
Adam Lesinski282e1812014-01-23 18:17:42 -08001422 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1423 error.string());
1424 goto bail;
1425 }
1426 } else if (tag == "uses-package") {
1427 String8 name = getAttribute(tree, NAME_ATTR, &error);
1428 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001429 printf("uses-package:'%s'\n",
1430 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001431 } else {
1432 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1433 error.string());
1434 goto bail;
1435 }
1436 } else if (tag == "original-package") {
1437 String8 name = getAttribute(tree, NAME_ATTR, &error);
1438 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001439 printf("original-package:'%s'\n",
1440 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001441 } else {
1442 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1443 error.string());
1444 goto bail;
1445 }
1446 } else if (tag == "supports-gl-texture") {
1447 String8 name = getAttribute(tree, NAME_ATTR, &error);
1448 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001449 printf("supports-gl-texture:'%s'\n",
1450 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001451 } else {
1452 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1453 error.string());
1454 goto bail;
1455 }
1456 } else if (tag == "compatible-screens") {
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001457 printCompatibleScreens(tree, &error);
1458 if (error != "") {
1459 fprintf(stderr, "ERROR getting compatible screens: %s\n",
1460 error.string());
1461 goto bail;
1462 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001463 depth--;
1464 } else if (tag == "package-verifier") {
1465 String8 name = getAttribute(tree, NAME_ATTR, &error);
1466 if (name != "" && error == "") {
1467 String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
1468 if (publicKey != "" && error == "") {
1469 printf("package-verifier: name='%s' publicKey='%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001470 ResTable::normalizeForOutput(name.string()).string(),
1471 ResTable::normalizeForOutput(publicKey.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001472 }
1473 }
1474 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001475 } else if (depth == 3) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001476 withinActivity = false;
1477 withinReceiver = false;
1478 withinService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001479 withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001480 hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001481 hasMetaHostPaymentCategory = false;
1482 hasMetaOffHostPaymentCategory = false;
1483 hasBindDeviceAdminPermission = false;
1484 hasBindInputMethodPermission = false;
1485 hasBindAccessibilityServicePermission = false;
1486 hasBindPrintServicePermission = false;
1487 hasBindNfcServicePermission = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001488 hasRequiredSafAttributes = false;
1489 hasBindNotificationListenerServicePermission = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001490 hasBindDreamServicePermission = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001491 if (withinApplication) {
1492 if(tag == "activity") {
1493 withinActivity = true;
1494 activityName = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001495 if (error != "") {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001496 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1497 error.string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001498 goto bail;
1499 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001500
Michael Wrightec4fdec2013-09-06 16:50:52 -07001501 activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1502 if (error != "") {
1503 fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
1504 error.string());
1505 goto bail;
1506 }
1507
1508 activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1509 if (error != "") {
1510 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
1511 error.string());
1512 goto bail;
1513 }
1514
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001515 activityBanner = getResolvedAttribute(&res, tree, BANNER_ATTR, &error);
1516 if (error != "") {
1517 fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
1518 error.string());
1519 goto bail;
1520 }
1521
Michael Wrightec4fdec2013-09-06 16:50:52 -07001522 int32_t orien = getResolvedIntegerAttribute(&res, tree,
1523 SCREEN_ORIENTATION_ATTR, &error);
1524 if (error == "") {
1525 if (orien == 0 || orien == 6 || orien == 8) {
1526 // Requests landscape, sensorLandscape, or reverseLandscape.
Adam Lesinski2c72b682014-06-24 09:56:01 -07001527 addImpliedFeature(&impliedFeatures, "android.hardware.screen.landscape",
1528 "one or more activities have specified a landscape orientation");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001529 } else if (orien == 1 || orien == 7 || orien == 9) {
1530 // Requests portrait, sensorPortrait, or reversePortrait.
Adam Lesinski2c72b682014-06-24 09:56:01 -07001531 addImpliedFeature(&impliedFeatures, "android.hardware.screen.portrait",
1532 "one or more activities have specified a portrait orientation");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001533 }
1534 }
1535 } else if (tag == "uses-library") {
1536 String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
1537 if (error != "") {
1538 fprintf(stderr,
1539 "ERROR getting 'android:name' attribute for uses-library"
1540 " %s\n", error.string());
1541 goto bail;
1542 }
1543 int req = getIntegerAttribute(tree,
1544 REQUIRED_ATTR, NULL, 1);
1545 printf("uses-library%s:'%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001546 req ? "" : "-not-required", ResTable::normalizeForOutput(
1547 libraryName.string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001548 } else if (tag == "receiver") {
1549 withinReceiver = true;
1550 receiverName = getAttribute(tree, NAME_ATTR, &error);
1551
1552 if (error != "") {
1553 fprintf(stderr,
1554 "ERROR getting 'android:name' attribute for receiver:"
1555 " %s\n", error.string());
1556 goto bail;
1557 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001558
1559 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1560 if (error == "") {
1561 if (permission == "android.permission.BIND_DEVICE_ADMIN") {
1562 hasBindDeviceAdminPermission = true;
1563 }
1564 } else {
1565 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1566 " receiver '%s': %s\n", receiverName.string(), error.string());
1567 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001568 } else if (tag == "service") {
1569 withinService = true;
1570 serviceName = getAttribute(tree, NAME_ATTR, &error);
1571
1572 if (error != "") {
1573 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1574 "service:%s\n", error.string());
1575 goto bail;
1576 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001577
1578 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1579 if (error == "") {
1580 if (permission == "android.permission.BIND_INPUT_METHOD") {
1581 hasBindInputMethodPermission = true;
1582 } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
1583 hasBindAccessibilityServicePermission = true;
1584 } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
1585 hasBindPrintServicePermission = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001586 } else if (permission == "android.permission.BIND_NFC_SERVICE") {
1587 hasBindNfcServicePermission = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001588 } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
1589 hasBindNotificationListenerServicePermission = true;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001590 } else if (permission == "android.permission.BIND_DREAM_SERVICE") {
1591 hasBindDreamServicePermission = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001592 }
1593 } else {
1594 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1595 " service '%s': %s\n", serviceName.string(), error.string());
1596 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001597 } else if (tag == "provider") {
1598 withinProvider = true;
1599
1600 bool exported = getResolvedIntegerAttribute(&res, tree, EXPORTED_ATTR, &error);
1601 if (error != "") {
1602 fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:"
1603 " %s\n", error.string());
1604 goto bail;
1605 }
1606
1607 bool grantUriPermissions = getResolvedIntegerAttribute(&res, tree,
1608 GRANT_URI_PERMISSIONS_ATTR, &error);
1609 if (error != "") {
1610 fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:"
1611 " %s\n", error.string());
1612 goto bail;
1613 }
1614
1615 String8 permission = getResolvedAttribute(&res, tree, PERMISSION_ATTR, &error);
1616 if (error != "") {
1617 fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:"
1618 " %s\n", error.string());
1619 goto bail;
1620 }
1621
1622 hasRequiredSafAttributes |= exported && grantUriPermissions &&
1623 permission == "android.permission.MANAGE_DOCUMENTS";
1624
Michael Wrightec4fdec2013-09-06 16:50:52 -07001625 } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
Adam Lesinskib71adb62014-05-15 14:14:41 -07001626 String8 metaDataName = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001627 if (error != "") {
1628 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1629 "meta-data:%s\n", error.string());
1630 goto bail;
1631 }
Maurice Chu2675f762013-10-22 17:33:11 -07001632 printf("meta-data: name='%s' ",
1633 ResTable::normalizeForOutput(metaDataName.string()).string());
Maurice Chu76327312013-10-16 18:28:46 -07001634 printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"),
1635 &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001636 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001637 // Try looking for a RESOURCE_ATTR
1638 error = "";
1639 printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR,
1640 String8("resource"), &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001641 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001642 fprintf(stderr, "ERROR getting 'android:value' or "
1643 "'android:resource' attribute for "
1644 "meta-data:%s\n", error.string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001645 goto bail;
1646 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001647 }
Maurice Chu76327312013-10-16 18:28:46 -07001648 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001649 } else if (withinSupportsInput && tag == "input-type") {
1650 String8 name = getAttribute(tree, NAME_ATTR, &error);
1651 if (name != "" && error == "") {
1652 supportedInput.add(name);
1653 } else {
1654 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1655 error.string());
1656 goto bail;
1657 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001658 }
Adam Lesinski2c72b682014-06-24 09:56:01 -07001659 } else if (withinFeatureGroup && tag == "uses-feature") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001660 FeatureGroup& top = featureGroups.editTop();
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001661
1662 String8 name = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
1663 if (name != "" && error == "") {
1664 int required = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
1665 top.features.add(name, required);
1666 if (required) {
1667 addParentFeatures(&top, name);
1668 }
1669
1670 } else {
1671 int vers = getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error);
1672 if (error == "") {
1673 if (vers > top.openGLESVersion) {
1674 top.openGLESVersion = vers;
1675 }
1676 }
Adam Lesinski2c72b682014-06-24 09:56:01 -07001677 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001678 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001679 } else if (depth == 4) {
1680 if (tag == "intent-filter") {
1681 hasIntentFilter = true;
1682 withinIntentFilter = true;
1683 actMainActivity = false;
1684 actWidgetReceivers = false;
1685 actImeService = false;
1686 actWallpaperService = false;
1687 actAccessibilityService = false;
1688 actPrintService = false;
1689 actDeviceAdminEnabled = false;
1690 actHostApduService = false;
1691 actOffHostApduService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001692 actDocumentsProvider = false;
1693 actNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001694 actDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001695 actCamera = false;
1696 actCameraSecure = false;
1697 catLauncher = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001698 } else if (withinService && tag == "meta-data") {
1699 String8 name = getAttribute(tree, NAME_ATTR, &error);
1700 if (error != "") {
1701 fprintf(stderr, "ERROR getting 'android:name' attribute for"
1702 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1703 goto bail;
1704 }
1705
1706 if (name == "android.nfc.cardemulation.host_apdu_service" ||
1707 name == "android.nfc.cardemulation.off_host_apdu_service") {
1708 bool offHost = true;
1709 if (name == "android.nfc.cardemulation.host_apdu_service") {
1710 offHost = false;
1711 }
1712
1713 String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
1714 if (error != "") {
1715 fprintf(stderr, "ERROR getting 'android:resource' attribute for"
1716 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1717 goto bail;
1718 }
1719
1720 Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
1721 offHost, &error);
1722 if (error != "") {
1723 fprintf(stderr, "ERROR getting AID category for service '%s'\n",
1724 serviceName.string());
1725 goto bail;
1726 }
1727
1728 const size_t catLen = categories.size();
1729 for (size_t i = 0; i < catLen; i++) {
1730 bool paymentCategory = (categories[i] == "payment");
1731 if (offHost) {
1732 hasMetaOffHostPaymentCategory |= paymentCategory;
1733 } else {
1734 hasMetaHostPaymentCategory |= paymentCategory;
1735 }
1736 }
1737 }
1738 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001739 } else if ((depth == 5) && withinIntentFilter) {
1740 String8 action;
1741 if (tag == "action") {
1742 action = getAttribute(tree, NAME_ATTR, &error);
1743 if (error != "") {
1744 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1745 error.string());
1746 goto bail;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001747 }
1748
Adam Lesinskia5018c92013-09-30 16:23:15 -07001749 if (withinActivity) {
1750 if (action == "android.intent.action.MAIN") {
1751 isMainActivity = true;
1752 actMainActivity = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001753 } else if (action == "android.media.action.STILL_IMAGE_CAMERA" ||
1754 action == "android.media.action.VIDEO_CAMERA") {
1755 actCamera = true;
1756 } else if (action == "android.media.action.STILL_IMAGE_CAMERA_SECURE") {
1757 actCameraSecure = true;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001758 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001759 } else if (withinReceiver) {
1760 if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
1761 actWidgetReceivers = true;
1762 } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
1763 actDeviceAdminEnabled = true;
1764 }
1765 } else if (withinService) {
1766 if (action == "android.view.InputMethod") {
1767 actImeService = true;
1768 } else if (action == "android.service.wallpaper.WallpaperService") {
1769 actWallpaperService = true;
1770 } else if (action == "android.accessibilityservice.AccessibilityService") {
1771 actAccessibilityService = true;
1772 } else if (action == "android.printservice.PrintService") {
1773 actPrintService = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001774 } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
1775 actHostApduService = true;
1776 } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
1777 actOffHostApduService = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001778 } else if (action == "android.service.notification.NotificationListenerService") {
1779 actNotificationListenerService = true;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001780 } else if (action == "android.service.dreams.DreamService") {
1781 actDreamService = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001782 }
1783 } else if (withinProvider) {
1784 if (action == "android.content.action.DOCUMENTS_PROVIDER") {
1785 actDocumentsProvider = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001786 }
1787 }
1788 if (action == "android.intent.action.SEARCH") {
1789 isSearchable = true;
1790 }
1791 }
1792
1793 if (tag == "category") {
1794 String8 category = getAttribute(tree, NAME_ATTR, &error);
1795 if (error != "") {
1796 fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
1797 error.string());
1798 goto bail;
1799 }
1800 if (withinActivity) {
1801 if (category == "android.intent.category.LAUNCHER") {
1802 isLauncherActivity = true;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001803 } else if (category == "android.intent.category.LEANBACK_LAUNCHER") {
1804 isLeanbackLauncherActivity = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001805 } else if (category == "android.intent.category.HOME") {
1806 catLauncher = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001807 }
1808 }
1809 }
1810 }
1811 }
1812
1813 // Pre-1.6 implicitly granted permission compatibility logic
1814 if (targetSdk < 4) {
1815 if (!hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001816 printUsesPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"));
1817 printUsesImpliedPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"),
1818 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001819 hasWriteExternalStoragePermission = true;
1820 }
1821 if (!hasReadPhoneStatePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001822 printUsesPermission(String8("android.permission.READ_PHONE_STATE"));
1823 printUsesImpliedPermission(String8("android.permission.READ_PHONE_STATE"),
1824 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001825 }
1826 }
1827
1828 // If the application has requested WRITE_EXTERNAL_STORAGE, we will
1829 // force them to always take READ_EXTERNAL_STORAGE as well. We always
1830 // do this (regardless of target API version) because we can't have
1831 // an app with write permission but not read permission.
1832 if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001833 printUsesPermission(String8("android.permission.READ_EXTERNAL_STORAGE"));
1834 printUsesImpliedPermission(String8("android.permission.READ_EXTERNAL_STORAGE"),
1835 String8("requested WRITE_EXTERNAL_STORAGE"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001836 }
1837
1838 // Pre-JellyBean call log permission compatibility.
1839 if (targetSdk < 16) {
1840 if (!hasReadCallLogPermission && hasReadContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001841 printUsesPermission(String8("android.permission.READ_CALL_LOG"));
1842 printUsesImpliedPermission(String8("android.permission.READ_CALL_LOG"),
1843 String8("targetSdkVersion < 16 and requested READ_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001844 }
1845 if (!hasWriteCallLogPermission && hasWriteContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001846 printUsesPermission(String8("android.permission.WRITE_CALL_LOG"));
1847 printUsesImpliedPermission(String8("android.permission.WRITE_CALL_LOG"),
1848 String8("targetSdkVersion < 16 and requested WRITE_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001849 }
1850 }
1851
Adam Lesinski2c72b682014-06-24 09:56:01 -07001852 addImpliedFeature(&impliedFeatures, "android.hardware.touchscreen",
1853 "default feature for all apps");
1854
1855 const size_t numFeatureGroups = featureGroups.size();
1856 if (numFeatureGroups == 0) {
1857 // If no <feature-group> tags were defined, apply auto-implied features.
1858 printFeatureGroup(commonFeatures, &impliedFeatures);
1859
1860 } else {
1861 // <feature-group> tags are defined, so we ignore implied features and
1862 for (size_t i = 0; i < numFeatureGroups; i++) {
1863 FeatureGroup& grp = featureGroups.editItemAt(i);
1864
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001865 if (commonFeatures.openGLESVersion > grp.openGLESVersion) {
1866 grp.openGLESVersion = commonFeatures.openGLESVersion;
1867 }
1868
Adam Lesinski2c72b682014-06-24 09:56:01 -07001869 // Merge the features defined in the top level (not inside a <feature-group>)
1870 // with this feature group.
1871 const size_t numCommonFeatures = commonFeatures.features.size();
1872 for (size_t j = 0; j < numCommonFeatures; j++) {
1873 if (grp.features.indexOfKey(commonFeatures.features.keyAt(j)) < 0) {
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001874 grp.features.add(commonFeatures.features.keyAt(j),
1875 commonFeatures.features[j]);
Adam Lesinski2c72b682014-06-24 09:56:01 -07001876 }
1877 }
1878
1879 if (!grp.features.isEmpty()) {
1880 printFeatureGroup(grp);
Adam Lesinski282e1812014-01-23 18:17:42 -08001881 }
1882 }
1883 }
1884
Adam Lesinski282e1812014-01-23 18:17:42 -08001885
Adam Lesinski282e1812014-01-23 18:17:42 -08001886 if (hasWidgetReceivers) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001887 printComponentPresence("app-widget");
Adam Lesinski282e1812014-01-23 18:17:42 -08001888 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001889 if (hasDeviceAdminReceiver) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001890 printComponentPresence("device-admin");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001891 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001892 if (hasImeService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001893 printComponentPresence("ime");
Adam Lesinski282e1812014-01-23 18:17:42 -08001894 }
1895 if (hasWallpaperService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001896 printComponentPresence("wallpaper");
Adam Lesinski282e1812014-01-23 18:17:42 -08001897 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001898 if (hasAccessibilityService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001899 printComponentPresence("accessibility");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001900 }
1901 if (hasPrintService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001902 printComponentPresence("print-service");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001903 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001904 if (hasPaymentService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001905 printComponentPresence("payment");
1906 }
1907 if (isSearchable) {
1908 printComponentPresence("search");
1909 }
1910 if (hasDocumentsProvider) {
1911 printComponentPresence("document-provider");
1912 }
1913 if (hasLauncher) {
1914 printComponentPresence("launcher");
1915 }
1916 if (hasNotificationListenerService) {
1917 printComponentPresence("notification-listener");
1918 }
John Spurlockeb8d1be2014-06-25 17:46:15 -04001919 if (hasDreamService) {
1920 printComponentPresence("dream");
1921 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001922 if (hasCameraActivity) {
1923 printComponentPresence("camera");
1924 }
1925 if (hasCameraSecureActivity) {
1926 printComponentPresence("camera-secure");
1927 }
1928
1929 if (hasMainActivity) {
1930 printf("main\n");
Adam Lesinski94fc9122013-09-30 17:16:09 -07001931 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001932 if (hasOtherActivities) {
1933 printf("other-activities\n");
1934 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001935 if (hasOtherReceivers) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001936 printf("other-receivers\n");
1937 }
1938 if (hasOtherServices) {
1939 printf("other-services\n");
1940 }
1941
1942 // For modern apps, if screen size buckets haven't been specified
1943 // but the new width ranges have, then infer the buckets from them.
1944 if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0
1945 && requiresSmallestWidthDp > 0) {
1946 int compatWidth = compatibleWidthLimitDp;
1947 if (compatWidth <= 0) {
1948 compatWidth = requiresSmallestWidthDp;
1949 }
1950 if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) {
1951 smallScreen = -1;
1952 } else {
1953 smallScreen = 0;
1954 }
1955 if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) {
1956 normalScreen = -1;
1957 } else {
1958 normalScreen = 0;
1959 }
1960 if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) {
1961 largeScreen = -1;
1962 } else {
1963 largeScreen = 0;
1964 }
1965 if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) {
1966 xlargeScreen = -1;
1967 } else {
1968 xlargeScreen = 0;
1969 }
1970 }
1971
1972 // Determine default values for any unspecified screen sizes,
1973 // based on the target SDK of the package. As of 4 (donut)
1974 // the screen size support was introduced, so all default to
1975 // enabled.
1976 if (smallScreen > 0) {
1977 smallScreen = targetSdk >= 4 ? -1 : 0;
1978 }
1979 if (normalScreen > 0) {
1980 normalScreen = -1;
1981 }
1982 if (largeScreen > 0) {
1983 largeScreen = targetSdk >= 4 ? -1 : 0;
1984 }
1985 if (xlargeScreen > 0) {
1986 // Introduced in Gingerbread.
1987 xlargeScreen = targetSdk >= 9 ? -1 : 0;
1988 }
1989 if (anyDensity > 0) {
1990 anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0
1991 || compatibleWidthLimitDp > 0) ? -1 : 0;
1992 }
1993 printf("supports-screens:");
1994 if (smallScreen != 0) {
1995 printf(" 'small'");
1996 }
1997 if (normalScreen != 0) {
1998 printf(" 'normal'");
1999 }
2000 if (largeScreen != 0) {
2001 printf(" 'large'");
2002 }
2003 if (xlargeScreen != 0) {
2004 printf(" 'xlarge'");
2005 }
2006 printf("\n");
2007 printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
2008 if (requiresSmallestWidthDp > 0) {
2009 printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp);
2010 }
2011 if (compatibleWidthLimitDp > 0) {
2012 printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp);
2013 }
2014 if (largestWidthLimitDp > 0) {
2015 printf("largest-width-limit:'%d'\n", largestWidthLimitDp);
2016 }
2017
2018 printf("locales:");
2019 const size_t NL = locales.size();
2020 for (size_t i=0; i<NL; i++) {
2021 const char* localeStr = locales[i].string();
2022 if (localeStr == NULL || strlen(localeStr) == 0) {
2023 localeStr = "--_--";
2024 }
2025 printf(" '%s'", localeStr);
2026 }
2027 printf("\n");
2028
2029 printf("densities:");
2030 const size_t ND = densities.size();
2031 for (size_t i=0; i<ND; i++) {
2032 printf(" '%d'", densities[i]);
2033 }
2034 printf("\n");
2035
2036 AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
2037 if (dir != NULL) {
2038 if (dir->getFileCount() > 0) {
2039 printf("native-code:");
2040 for (size_t i=0; i<dir->getFileCount(); i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07002041 printf(" '%s'", ResTable::normalizeForOutput(
2042 dir->getFileName(i).string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002043 }
2044 printf("\n");
2045 }
2046 delete dir;
2047 }
2048 } else if (strcmp("badger", option) == 0) {
2049 printf("%s", CONSOLE_DATA);
2050 } else if (strcmp("configurations", option) == 0) {
2051 Vector<ResTable_config> configs;
2052 res.getConfigurations(&configs);
2053 const size_t N = configs.size();
2054 for (size_t i=0; i<N; i++) {
2055 printf("%s\n", configs[i].toString().string());
2056 }
2057 } else {
2058 fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
2059 goto bail;
2060 }
2061 }
2062
2063 result = NO_ERROR;
2064
2065bail:
2066 if (asset) {
2067 delete asset;
2068 }
2069 return (result != NO_ERROR);
2070}
2071
2072
2073/*
2074 * Handle the "add" command, which wants to add files to a new or
2075 * pre-existing archive.
2076 */
2077int doAdd(Bundle* bundle)
2078{
2079 ZipFile* zip = NULL;
2080 status_t result = UNKNOWN_ERROR;
2081 const char* zipFileName;
2082
2083 if (bundle->getUpdate()) {
2084 /* avoid confusion */
2085 fprintf(stderr, "ERROR: can't use '-u' with add\n");
2086 goto bail;
2087 }
2088
2089 if (bundle->getFileSpecCount() < 1) {
2090 fprintf(stderr, "ERROR: must specify zip file name\n");
2091 goto bail;
2092 }
2093 zipFileName = bundle->getFileSpecEntry(0);
2094
2095 if (bundle->getFileSpecCount() < 2) {
2096 fprintf(stderr, "NOTE: nothing to do\n");
2097 goto bail;
2098 }
2099
2100 zip = openReadWrite(zipFileName, true);
2101 if (zip == NULL) {
2102 fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
2103 goto bail;
2104 }
2105
2106 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2107 const char* fileName = bundle->getFileSpecEntry(i);
2108
2109 if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
2110 printf(" '%s'... (from gzip)\n", fileName);
2111 result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
2112 } else {
2113 if (bundle->getJunkPath()) {
2114 String8 storageName = String8(fileName).getPathLeaf();
Maurice Chu2675f762013-10-22 17:33:11 -07002115 printf(" '%s' as '%s'...\n", fileName,
2116 ResTable::normalizeForOutput(storageName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002117 result = zip->add(fileName, storageName.string(),
2118 bundle->getCompressionMethod(), NULL);
2119 } else {
2120 printf(" '%s'...\n", fileName);
2121 result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
2122 }
2123 }
2124 if (result != NO_ERROR) {
2125 fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
2126 if (result == NAME_NOT_FOUND) {
2127 fprintf(stderr, ": file not found\n");
2128 } else if (result == ALREADY_EXISTS) {
2129 fprintf(stderr, ": already exists in archive\n");
2130 } else {
2131 fprintf(stderr, "\n");
2132 }
2133 goto bail;
2134 }
2135 }
2136
2137 result = NO_ERROR;
2138
2139bail:
2140 delete zip;
2141 return (result != NO_ERROR);
2142}
2143
2144
2145/*
2146 * Delete files from an existing archive.
2147 */
2148int doRemove(Bundle* bundle)
2149{
2150 ZipFile* zip = NULL;
2151 status_t result = UNKNOWN_ERROR;
2152 const char* zipFileName;
2153
2154 if (bundle->getFileSpecCount() < 1) {
2155 fprintf(stderr, "ERROR: must specify zip file name\n");
2156 goto bail;
2157 }
2158 zipFileName = bundle->getFileSpecEntry(0);
2159
2160 if (bundle->getFileSpecCount() < 2) {
2161 fprintf(stderr, "NOTE: nothing to do\n");
2162 goto bail;
2163 }
2164
2165 zip = openReadWrite(zipFileName, false);
2166 if (zip == NULL) {
2167 fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
2168 zipFileName);
2169 goto bail;
2170 }
2171
2172 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2173 const char* fileName = bundle->getFileSpecEntry(i);
2174 ZipEntry* entry;
2175
2176 entry = zip->getEntryByName(fileName);
2177 if (entry == NULL) {
2178 printf(" '%s' NOT FOUND\n", fileName);
2179 continue;
2180 }
2181
2182 result = zip->remove(entry);
2183
2184 if (result != NO_ERROR) {
2185 fprintf(stderr, "Unable to delete '%s' from '%s'\n",
2186 bundle->getFileSpecEntry(i), zipFileName);
2187 goto bail;
2188 }
2189 }
2190
2191 /* update the archive */
2192 zip->flush();
2193
2194bail:
2195 delete zip;
2196 return (result != NO_ERROR);
2197}
2198
Adam Lesinski3921e872014-05-13 10:56:25 -07002199static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder, bool ignoreConfig=false) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002200 const size_t numDirs = dir->getDirs().size();
2201 for (size_t i = 0; i < numDirs; i++) {
Adam Lesinski3921e872014-05-13 10:56:25 -07002202 bool ignore = ignoreConfig;
2203 const sp<AaptDir>& subDir = dir->getDirs().valueAt(i);
2204 const char* dirStr = subDir->getLeaf().string();
2205 if (!ignore && strstr(dirStr, "mipmap") == dirStr) {
2206 ignore = true;
2207 }
2208 status_t err = addResourcesToBuilder(subDir, builder, ignore);
Adam Lesinskifab50872014-04-16 14:40:42 -07002209 if (err != NO_ERROR) {
2210 return err;
2211 }
2212 }
2213
2214 const size_t numFiles = dir->getFiles().size();
2215 for (size_t i = 0; i < numFiles; i++) {
2216 sp<AaptGroup> gp = dir->getFiles().valueAt(i);
2217 const size_t numConfigs = gp->getFiles().size();
2218 for (size_t j = 0; j < numConfigs; j++) {
Adam Lesinski3921e872014-05-13 10:56:25 -07002219 status_t err = NO_ERROR;
2220 if (ignoreConfig) {
2221 err = builder->getBaseSplit()->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2222 } else {
2223 err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2224 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002225 if (err != NO_ERROR) {
2226 fprintf(stderr, "Failed to add %s (%s) to builder.\n",
2227 gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string());
2228 return err;
2229 }
2230 }
2231 }
2232 return NO_ERROR;
2233}
2234
2235static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) {
2236 if (split->isBase()) {
2237 return original;
2238 }
2239
2240 String8 ext(original.getPathExtension());
2241 if (ext == String8(".apk")) {
2242 return String8::format("%s_%s%s",
2243 original.getBasePath().string(),
2244 split->getDirectorySafeName().string(),
2245 ext.string());
2246 }
2247
2248 return String8::format("%s_%s", original.string(),
2249 split->getDirectorySafeName().string());
2250}
Adam Lesinski282e1812014-01-23 18:17:42 -08002251
2252/*
2253 * Package up an asset directory and associated application files.
2254 */
2255int doPackage(Bundle* bundle)
2256{
2257 const char* outputAPKFile;
2258 int retVal = 1;
2259 status_t err;
2260 sp<AaptAssets> assets;
2261 int N;
2262 FILE* fp;
2263 String8 dependencyFile;
Adam Lesinskifab50872014-04-16 14:40:42 -07002264 sp<ApkBuilder> builder;
Adam Lesinski282e1812014-01-23 18:17:42 -08002265
Anton Krumina2ef5c02014-03-12 14:46:44 -07002266 // -c en_XA or/and ar_XB means do pseudolocalization
Adam Lesinskifab50872014-04-16 14:40:42 -07002267 sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
2268 err = configFilter->parse(bundle->getConfigurations());
Adam Lesinski282e1812014-01-23 18:17:42 -08002269 if (err != NO_ERROR) {
2270 goto bail;
2271 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002272 if (configFilter->containsPseudo()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002273 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
2274 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002275 if (configFilter->containsPseudoBidi()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002276 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
Adam Lesinski282e1812014-01-23 18:17:42 -08002277 }
2278
2279 N = bundle->getFileSpecCount();
2280 if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
Adam Lesinski09384302014-01-22 16:07:42 -08002281 && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002282 fprintf(stderr, "ERROR: no input files\n");
2283 goto bail;
2284 }
2285
2286 outputAPKFile = bundle->getOutputAPKFile();
2287
2288 // Make sure the filenames provided exist and are of the appropriate type.
2289 if (outputAPKFile) {
2290 FileType type;
2291 type = getFileType(outputAPKFile);
2292 if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
2293 fprintf(stderr,
2294 "ERROR: output file '%s' exists but is not regular file\n",
2295 outputAPKFile);
2296 goto bail;
2297 }
2298 }
2299
2300 // Load the assets.
2301 assets = new AaptAssets();
2302
2303 // Set up the resource gathering in assets if we're going to generate
2304 // dependency files. Every time we encounter a resource while slurping
2305 // the tree, we'll add it to these stores so we have full resource paths
2306 // to write to a dependency file.
2307 if (bundle->getGenDependencies()) {
2308 sp<FilePathStore> resPathStore = new FilePathStore;
2309 assets->setFullResPaths(resPathStore);
2310 sp<FilePathStore> assetPathStore = new FilePathStore;
2311 assets->setFullAssetPaths(assetPathStore);
2312 }
2313
2314 err = assets->slurpFromArgs(bundle);
2315 if (err < 0) {
2316 goto bail;
2317 }
2318
2319 if (bundle->getVerbose()) {
2320 assets->print(String8());
2321 }
2322
Adam Lesinskifab50872014-04-16 14:40:42 -07002323 // Create the ApkBuilder, which will collect the compiled files
2324 // to write to the final APK (or sets of APKs if we are building
2325 // a Split APK.
2326 builder = new ApkBuilder(configFilter);
2327
2328 // If we are generating a Split APK, find out which configurations to split on.
2329 if (bundle->getSplitConfigurations().size() > 0) {
2330 const Vector<String8>& splitStrs = bundle->getSplitConfigurations();
2331 const size_t numSplits = splitStrs.size();
2332 for (size_t i = 0; i < numSplits; i++) {
2333 std::set<ConfigDescription> configs;
2334 if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {
2335 fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());
2336 goto bail;
2337 }
2338
2339 err = builder->createSplitForConfigs(configs);
2340 if (err != NO_ERROR) {
2341 goto bail;
2342 }
2343 }
2344 }
2345
Adam Lesinski282e1812014-01-23 18:17:42 -08002346 // If they asked for any fileAs that need to be compiled, do so.
2347 if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002348 err = buildResources(bundle, assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002349 if (err != 0) {
2350 goto bail;
2351 }
2352 }
2353
2354 // At this point we've read everything and processed everything. From here
2355 // on out it's just writing output files.
2356 if (SourcePos::hasErrors()) {
2357 goto bail;
2358 }
2359
2360 // Update symbols with information about which ones are needed as Java symbols.
2361 assets->applyJavaSymbols();
2362 if (SourcePos::hasErrors()) {
2363 goto bail;
2364 }
2365
2366 // If we've been asked to generate a dependency file, do that here
2367 if (bundle->getGenDependencies()) {
2368 // If this is the packaging step, generate the dependency file next to
2369 // the output apk (e.g. bin/resources.ap_.d)
2370 if (outputAPKFile) {
2371 dependencyFile = String8(outputAPKFile);
2372 // Add the .d extension to the dependency file.
2373 dependencyFile.append(".d");
2374 } else {
2375 // Else if this is the R.java dependency generation step,
2376 // generate the dependency file in the R.java package subdirectory
2377 // e.g. gen/com/foo/app/R.java.d
2378 dependencyFile = String8(bundle->getRClassDir());
2379 dependencyFile.appendPath("R.java.d");
2380 }
2381 // Make sure we have a clean dependency file to start with
2382 fp = fopen(dependencyFile, "w");
2383 fclose(fp);
2384 }
2385
2386 // Write out R.java constants
2387 if (!assets->havePrivateSymbols()) {
2388 if (bundle->getCustomPackage() == NULL) {
2389 // Write the R.java file into the appropriate class directory
2390 // e.g. gen/com/foo/app/R.java
2391 err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
2392 } else {
2393 const String8 customPkg(bundle->getCustomPackage());
2394 err = writeResourceSymbols(bundle, assets, customPkg, true);
2395 }
2396 if (err < 0) {
2397 goto bail;
2398 }
2399 // If we have library files, we're going to write our R.java file into
2400 // the appropriate class directory for those libraries as well.
2401 // e.g. gen/com/foo/app/lib/R.java
2402 if (bundle->getExtraPackages() != NULL) {
2403 // Split on colon
2404 String8 libs(bundle->getExtraPackages());
2405 char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
2406 while (packageString != NULL) {
2407 // Write the R.java file out with the correct package name
2408 err = writeResourceSymbols(bundle, assets, String8(packageString), true);
2409 if (err < 0) {
2410 goto bail;
2411 }
2412 packageString = strtok(NULL, ":");
2413 }
2414 libs.unlockBuffer();
2415 }
2416 } else {
2417 err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
2418 if (err < 0) {
2419 goto bail;
2420 }
2421 err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
2422 if (err < 0) {
2423 goto bail;
2424 }
2425 }
2426
2427 // Write out the ProGuard file
2428 err = writeProguardFile(bundle, assets);
2429 if (err < 0) {
2430 goto bail;
2431 }
2432
2433 // Write the apk
2434 if (outputAPKFile) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002435 // Gather all resources and add them to the APK Builder. The builder will then
2436 // figure out which Split they belong in.
2437 err = addResourcesToBuilder(assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002438 if (err != NO_ERROR) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002439 goto bail;
2440 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002441
2442 const Vector<sp<ApkSplit> >& splits = builder->getSplits();
2443 const size_t numSplits = splits.size();
2444 for (size_t i = 0; i < numSplits; i++) {
2445 const sp<ApkSplit>& split = splits[i];
2446 String8 outputPath = buildApkName(String8(outputAPKFile), split);
2447 err = writeAPK(bundle, outputPath, split);
2448 if (err != NO_ERROR) {
2449 fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
2450 goto bail;
2451 }
2452 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002453 }
2454
2455 // If we've been asked to generate a dependency file, we need to finish up here.
2456 // the writeResourceSymbols and writeAPK functions have already written the target
2457 // half of the dependency file, now we need to write the prerequisites. (files that
2458 // the R.java file or .ap_ file depend on)
2459 if (bundle->getGenDependencies()) {
2460 // Now that writeResourceSymbols or writeAPK has taken care of writing
2461 // the targets to our dependency file, we'll write the prereqs
2462 fp = fopen(dependencyFile, "a+");
2463 fprintf(fp, " : ");
2464 bool includeRaw = (outputAPKFile != NULL);
2465 err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
2466 // Also manually add the AndroidManifeset since it's not under res/ or assets/
2467 // and therefore was not added to our pathstores during slurping
2468 fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
2469 fclose(fp);
2470 }
2471
2472 retVal = 0;
2473bail:
2474 if (SourcePos::hasErrors()) {
2475 SourcePos::printErrors(stderr);
2476 }
2477 return retVal;
2478}
2479
2480/*
2481 * Do PNG Crunching
2482 * PRECONDITIONS
2483 * -S flag points to a source directory containing drawable* folders
2484 * -C flag points to destination directory. The folder structure in the
2485 * source directory will be mirrored to the destination (cache) directory
2486 *
2487 * POSTCONDITIONS
2488 * Destination directory will be updated to match the PNG files in
Maurice Chu2675f762013-10-22 17:33:11 -07002489 * the source directory.
Adam Lesinski282e1812014-01-23 18:17:42 -08002490 */
2491int doCrunch(Bundle* bundle)
2492{
2493 fprintf(stdout, "Crunching PNG Files in ");
2494 fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
2495 fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
2496
2497 updatePreProcessedCache(bundle);
2498
2499 return NO_ERROR;
2500}
2501
2502/*
2503 * Do PNG Crunching on a single flag
2504 * -i points to a single png file
2505 * -o points to a single png output file
2506 */
2507int doSingleCrunch(Bundle* bundle)
2508{
2509 fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile());
2510 fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile());
2511
2512 String8 input(bundle->getSingleCrunchInputFile());
2513 String8 output(bundle->getSingleCrunchOutputFile());
2514
2515 if (preProcessImageToCache(bundle, input, output) != NO_ERROR) {
2516 // we can't return the status_t as it gets truncate to the lower 8 bits.
2517 return 42;
2518 }
2519
2520 return NO_ERROR;
2521}
2522
2523char CONSOLE_DATA[2925] = {
2524 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2525 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2526 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2527 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2528 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63,
2529 86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83,
2530 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2531 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2532 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81,
2533 81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32,
2534 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2535 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2536 32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59,
2537 59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2538 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2539 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59,
2540 59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32,
2541 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2542 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2543 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87,
2544 58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32,
2545 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2546 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2547 47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58,
2548 121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2549 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2550 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81,
2551 81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32,
2552 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2553 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2554 32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59,
2555 59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2556 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2557 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59,
2558 59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32,
2559 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2560 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2561 32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81,
2562 70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32,
2563 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2564 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2565 32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81,
2566 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2567 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2568 32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109,
2569 81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32,
2570 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2571 32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59,
2572 59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59,
2573 60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2574 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59,
2575 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46,
2576 61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32,
2577 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2578 32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2579 59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81,
2580 109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32,
2581 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81,
2582 67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2583 59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58,
2584 61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32,
2585 32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59,
2586 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37,
2587 73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2588 59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2589 32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59,
2590 59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96,
2591 46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61,
2592 97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32,
2593 46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61,
2594 119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59,
2595 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32,
2596 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2597 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119,
2598 119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2599 59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10,
2600 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2601 32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81,
2602 81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41,
2603 87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2604 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81,
2605 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58,
2606 45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32,
2607 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2608 32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2609 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32,
2610 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2611 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58,
2612 59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58,
2613 59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2614 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2615 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81,
2616 81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32,
2617 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2618 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2619 32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2620 81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2621 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2622 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106,
2623 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59,
2624 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2625 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2626 32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2627 81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32,
2628 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2629 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2630 58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59,
2631 61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2632 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2633 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81,
2634 81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32,
2635 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2636 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2637 32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2638 81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2639 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2640 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59,
2641 59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59,
2642 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2643 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2644 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33,
2645 58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32,
2646 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2647 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2648 61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59,
2649 59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32,
2650 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32,
2651 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95,
2652 32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59,
2653 61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2654 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2655 32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45,
2656 59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32,
2657 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32,
2658 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59,
2659 59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59,
2660 59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2661 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2662 32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2663 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2664 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2665 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2666 46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61,
2667 46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2668 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2669 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59,
2670 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59,
2671 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2672 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2673 32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32,
2674 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32,
2675 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2676 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61,
2677 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59,
2678 59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2679 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2680 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32,
2681 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32,
2682 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2683 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2684 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2685 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2686 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10
2687 };