blob: c74a3730bcba5298a3c983d847330146e0dc857a [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;
Adam Lesinski282e1812014-01-23 18:17:42 -0800717
718 if (bundle->getFileSpecCount() < 1) {
719 fprintf(stderr, "ERROR: no dump option specified\n");
720 return 1;
721 }
722
723 if (bundle->getFileSpecCount() < 2) {
724 fprintf(stderr, "ERROR: no dump file specified\n");
725 return 1;
726 }
727
728 const char* option = bundle->getFileSpecEntry(0);
729 const char* filename = bundle->getFileSpecEntry(1);
730
731 AssetManager assets;
Narayan Kamathf85e41f2014-01-24 14:14:30 +0000732 int32_t assetsCookie;
Adam Lesinski282e1812014-01-23 18:17:42 -0800733 if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
734 fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
735 return 1;
736 }
737
738 // Make a dummy config for retrieving resources... we need to supply
739 // non-default values for some configs so that we can retrieve resources
740 // in the app that don't have a default. The most important of these is
741 // the API version because key resources like icons will have an implicit
742 // version if they are using newer config types like density.
743 ResTable_config config;
Narayan Kamath91447d82014-01-21 15:32:36 +0000744 memset(&config, 0, sizeof(ResTable_config));
Adam Lesinski282e1812014-01-23 18:17:42 -0800745 config.language[0] = 'e';
746 config.language[1] = 'n';
747 config.country[0] = 'U';
748 config.country[1] = 'S';
749 config.orientation = ResTable_config::ORIENTATION_PORT;
750 config.density = ResTable_config::DENSITY_MEDIUM;
751 config.sdkVersion = 10000; // Very high.
752 config.screenWidthDp = 320;
753 config.screenHeightDp = 480;
754 config.smallestScreenWidthDp = 320;
Adam Lesinskic2dea8d2014-08-04 16:40:41 -0700755 config.screenLayout |= ResTable_config::SCREENSIZE_NORMAL;
Adam Lesinski282e1812014-01-23 18:17:42 -0800756 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");
Adam Lesinski63e646e2014-07-30 11:40:39 -0700761 return 1;
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");
Adam Lesinski63e646e2014-07-30 11:40:39 -0700764 return 1;
Adam Lesinski282e1812014-01-23 18:17:42 -0800765 }
766
Adam Lesinski2cb761e2014-08-15 13:59:02 -0700767 // The dynamicRefTable can be null if there are no resources for this asset cookie.
768 // This fine.
Adam Lesinski63e646e2014-07-30 11:40:39 -0700769 const DynamicRefTable* dynamicRefTable = res.getDynamicRefTableForCookie(assetsCookie);
Adam Lesinski63e646e2014-07-30 11:40:39 -0700770
771 Asset* asset = NULL;
772
Adam Lesinski282e1812014-01-23 18:17:42 -0800773 if (strcmp("resources", option) == 0) {
774#ifndef HAVE_ANDROID_OS
775 res.print(bundle->getValues());
776#endif
777
778 } else if (strcmp("strings", option) == 0) {
779 const ResStringPool* pool = res.getTableStringBlock(0);
780 printStringPool(pool);
781
782 } else if (strcmp("xmltree", option) == 0) {
783 if (bundle->getFileSpecCount() < 3) {
784 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
785 goto bail;
786 }
787
788 for (int i=2; i<bundle->getFileSpecCount(); i++) {
789 const char* resname = bundle->getFileSpecEntry(i);
Adam Lesinski63e646e2014-07-30 11:40:39 -0700790 ResXMLTree tree(dynamicRefTable);
791 asset = assets.openNonAsset(assetsCookie, resname, Asset::ACCESS_BUFFER);
Adam Lesinski282e1812014-01-23 18:17:42 -0800792 if (asset == NULL) {
793 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
794 goto bail;
795 }
796
797 if (tree.setTo(asset->getBuffer(true),
798 asset->getLength()) != NO_ERROR) {
799 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
800 goto bail;
801 }
802 tree.restart();
803 printXMLBlock(&tree);
804 tree.uninit();
805 delete asset;
806 asset = NULL;
807 }
808
809 } else if (strcmp("xmlstrings", option) == 0) {
810 if (bundle->getFileSpecCount() < 3) {
811 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
812 goto bail;
813 }
814
815 for (int i=2; i<bundle->getFileSpecCount(); i++) {
816 const char* resname = bundle->getFileSpecEntry(i);
Adam Lesinski63e646e2014-07-30 11:40:39 -0700817 asset = assets.openNonAsset(assetsCookie, resname, Asset::ACCESS_BUFFER);
Adam Lesinski282e1812014-01-23 18:17:42 -0800818 if (asset == NULL) {
819 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
820 goto bail;
821 }
822
Adam Lesinski63e646e2014-07-30 11:40:39 -0700823 ResXMLTree tree(dynamicRefTable);
Adam Lesinski282e1812014-01-23 18:17:42 -0800824 if (tree.setTo(asset->getBuffer(true),
825 asset->getLength()) != NO_ERROR) {
826 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
827 goto bail;
828 }
829 printStringPool(&tree.getStrings());
830 delete asset;
831 asset = NULL;
832 }
833
834 } else {
Adam Lesinski63e646e2014-07-30 11:40:39 -0700835 asset = assets.openNonAsset(assetsCookie, "AndroidManifest.xml", Asset::ACCESS_BUFFER);
Adam Lesinski282e1812014-01-23 18:17:42 -0800836 if (asset == NULL) {
837 fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
838 goto bail;
839 }
840
Adam Lesinski63e646e2014-07-30 11:40:39 -0700841 ResXMLTree tree(dynamicRefTable);
Adam Lesinski282e1812014-01-23 18:17:42 -0800842 if (tree.setTo(asset->getBuffer(true),
843 asset->getLength()) != NO_ERROR) {
844 fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
845 goto bail;
846 }
847 tree.restart();
848
849 if (strcmp("permissions", option) == 0) {
850 size_t len;
851 ResXMLTree::event_code_t code;
852 int depth = 0;
853 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
854 if (code == ResXMLTree::END_TAG) {
855 depth--;
856 continue;
857 }
858 if (code != ResXMLTree::START_TAG) {
859 continue;
860 }
861 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700862 const char16_t* ctag16 = tree.getElementName(&len);
863 if (ctag16 == NULL) {
864 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
865 goto bail;
866 }
867 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -0800868 //printf("Depth %d tag %s\n", depth, tag.string());
869 if (depth == 1) {
870 if (tag != "manifest") {
871 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
872 goto bail;
873 }
874 String8 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -0700875 printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800876 } else if (depth == 2 && tag == "permission") {
877 String8 error;
878 String8 name = getAttribute(tree, NAME_ATTR, &error);
879 if (error != "") {
880 fprintf(stderr, "ERROR: %s\n", error.string());
881 goto bail;
882 }
Maurice Chu2675f762013-10-22 17:33:11 -0700883 printf("permission: %s\n",
884 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800885 } else if (depth == 2 && tag == "uses-permission") {
886 String8 error;
887 String8 name = getAttribute(tree, NAME_ATTR, &error);
888 if (error != "") {
889 fprintf(stderr, "ERROR: %s\n", error.string());
890 goto bail;
891 }
Adam Lesinski58f1f362013-11-12 12:59:08 -0800892 printUsesPermission(name,
893 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
894 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
Adam Lesinski282e1812014-01-23 18:17:42 -0800895 }
896 }
897 } else if (strcmp("badging", option) == 0) {
898 Vector<String8> locales;
899 res.getLocales(&locales);
900
901 Vector<ResTable_config> configs;
902 res.getConfigurations(&configs);
903 SortedVector<int> densities;
904 const size_t NC = configs.size();
905 for (size_t i=0; i<NC; i++) {
906 int dens = configs[i].density;
907 if (dens == 0) {
908 dens = 160;
909 }
910 densities.add(dens);
911 }
912
913 size_t len;
914 ResXMLTree::event_code_t code;
915 int depth = 0;
916 String8 error;
917 bool withinActivity = false;
918 bool isMainActivity = false;
919 bool isLauncherActivity = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800920 bool isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800921 bool isSearchable = false;
922 bool withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700923 bool withinSupportsInput = false;
Adam Lesinski2c72b682014-06-24 09:56:01 -0700924 bool withinFeatureGroup = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800925 bool withinReceiver = false;
926 bool withinService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700927 bool withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800928 bool withinIntentFilter = false;
929 bool hasMainActivity = false;
930 bool hasOtherActivities = false;
931 bool hasOtherReceivers = false;
932 bool hasOtherServices = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700933 bool hasIntentFilter = false;
934
Adam Lesinski282e1812014-01-23 18:17:42 -0800935 bool hasWallpaperService = false;
936 bool hasImeService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700937 bool hasAccessibilityService = false;
938 bool hasPrintService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800939 bool hasWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700940 bool hasDeviceAdminReceiver = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700941 bool hasPaymentService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700942 bool hasDocumentsProvider = false;
943 bool hasCameraActivity = false;
944 bool hasCameraSecureActivity = false;
945 bool hasLauncher = false;
946 bool hasNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400947 bool hasDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700948
Adam Lesinski282e1812014-01-23 18:17:42 -0800949 bool actMainActivity = false;
950 bool actWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700951 bool actDeviceAdminEnabled = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800952 bool actImeService = false;
953 bool actWallpaperService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700954 bool actAccessibilityService = false;
955 bool actPrintService = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700956 bool actHostApduService = false;
957 bool actOffHostApduService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700958 bool actDocumentsProvider = false;
959 bool actNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400960 bool actDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700961 bool actCamera = false;
962 bool actCameraSecure = false;
963 bool catLauncher = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700964 bool hasMetaHostPaymentCategory = false;
965 bool hasMetaOffHostPaymentCategory = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700966
967 // These permissions are required by services implementing services
968 // the system binds to (IME, Accessibility, PrintServices, etc.)
969 bool hasBindDeviceAdminPermission = false;
970 bool hasBindInputMethodPermission = false;
971 bool hasBindAccessibilityServicePermission = false;
972 bool hasBindPrintServicePermission = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700973 bool hasBindNfcServicePermission = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700974 bool hasRequiredSafAttributes = false;
975 bool hasBindNotificationListenerServicePermission = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400976 bool hasBindDreamServicePermission = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800977
978 // These two implement the implicit permissions that are granted
979 // to pre-1.6 applications.
980 bool hasWriteExternalStoragePermission = false;
981 bool hasReadPhoneStatePermission = false;
982
983 // If an app requests write storage, they will also get read storage.
984 bool hasReadExternalStoragePermission = false;
985
986 // Implement transition to read and write call log.
987 bool hasReadContactsPermission = false;
988 bool hasWriteContactsPermission = false;
989 bool hasReadCallLogPermission = false;
990 bool hasWriteCallLogPermission = false;
991
992 // This next group of variables is used to implement a group of
993 // backward-compatibility heuristics necessitated by the addition of
994 // some new uses-feature constants in 2.1 and 2.2. In most cases, the
995 // heuristic is "if an app requests a permission but doesn't explicitly
996 // request the corresponding <uses-feature>, presume it's there anyway".
Adam Lesinski2c72b682014-06-24 09:56:01 -0700997
Adam Lesinski282e1812014-01-23 18:17:42 -0800998 // 2.2 also added some other features that apps can request, but that
999 // have no corresponding permission, so we cannot implement any
1000 // back-compatibility heuristic for them. The below are thus unnecessary
1001 // (but are retained here for documentary purposes.)
1002 //bool specCompassFeature = false;
1003 //bool specAccelerometerFeature = false;
1004 //bool specProximityFeature = false;
1005 //bool specAmbientLightFeature = false;
1006 //bool specLiveWallpaperFeature = false;
1007
1008 int targetSdk = 0;
1009 int smallScreen = 1;
1010 int normalScreen = 1;
1011 int largeScreen = 1;
1012 int xlargeScreen = 1;
1013 int anyDensity = 1;
1014 int requiresSmallestWidthDp = 0;
1015 int compatibleWidthLimitDp = 0;
1016 int largestWidthLimitDp = 0;
1017 String8 pkg;
1018 String8 activityName;
1019 String8 activityLabel;
1020 String8 activityIcon;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001021 String8 activityBanner;
Adam Lesinski282e1812014-01-23 18:17:42 -08001022 String8 receiverName;
1023 String8 serviceName;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001024 Vector<String8> supportedInput;
Adam Lesinski2c72b682014-06-24 09:56:01 -07001025
1026 FeatureGroup commonFeatures;
1027 Vector<FeatureGroup> featureGroups;
1028 KeyedVector<String8, ImpliedFeature> impliedFeatures;
1029
Adam Lesinski282e1812014-01-23 18:17:42 -08001030 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1031 if (code == ResXMLTree::END_TAG) {
1032 depth--;
1033 if (depth < 2) {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001034 if (withinSupportsInput && !supportedInput.isEmpty()) {
1035 printf("supports-input: '");
1036 const size_t N = supportedInput.size();
1037 for (size_t i=0; i<N; i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07001038 printf("%s", ResTable::normalizeForOutput(
1039 supportedInput[i].string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001040 if (i != N - 1) {
1041 printf("' '");
1042 } else {
1043 printf("'\n");
1044 }
1045 }
1046 supportedInput.clear();
1047 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001048 withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001049 withinSupportsInput = false;
Adam Lesinski2c72b682014-06-24 09:56:01 -07001050 withinFeatureGroup = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001051 } else if (depth < 3) {
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001052 if (withinActivity && isMainActivity) {
Maurice Chu2675f762013-10-22 17:33:11 -07001053 String8 aName(getComponentName(pkg, activityName));
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001054 if (isLauncherActivity) {
1055 printf("launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001056 if (aName.length() > 0) {
1057 printf(" name='%s' ",
1058 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001059 }
1060 printf(" label='%s' icon='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001061 ResTable::normalizeForOutput(activityLabel.string()).string(),
1062 ResTable::normalizeForOutput(activityIcon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001063 }
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001064 if (isLeanbackLauncherActivity) {
1065 printf("leanback-launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001066 if (aName.length() > 0) {
1067 printf(" name='%s' ",
1068 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001069 }
1070 printf(" label='%s' icon='%s' banner='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001071 ResTable::normalizeForOutput(activityLabel.string()).string(),
1072 ResTable::normalizeForOutput(activityIcon.string()).string(),
1073 ResTable::normalizeForOutput(activityBanner.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001074 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001075 }
1076 if (!hasIntentFilter) {
1077 hasOtherActivities |= withinActivity;
1078 hasOtherReceivers |= withinReceiver;
1079 hasOtherServices |= withinService;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001080 } else {
1081 if (withinService) {
1082 hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
1083 hasBindNfcServicePermission);
1084 hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
1085 hasBindNfcServicePermission);
1086 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001087 }
1088 withinActivity = false;
1089 withinService = false;
1090 withinReceiver = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001091 withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001092 hasIntentFilter = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001093 isMainActivity = isLauncherActivity = isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001094 } else if (depth < 4) {
1095 if (withinIntentFilter) {
1096 if (withinActivity) {
1097 hasMainActivity |= actMainActivity;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001098 hasLauncher |= catLauncher;
1099 hasCameraActivity |= actCamera;
1100 hasCameraSecureActivity |= actCameraSecure;
1101 hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure;
Adam Lesinski282e1812014-01-23 18:17:42 -08001102 } else if (withinReceiver) {
1103 hasWidgetReceivers |= actWidgetReceivers;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001104 hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
1105 hasBindDeviceAdminPermission);
1106 hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
Adam Lesinski282e1812014-01-23 18:17:42 -08001107 } else if (withinService) {
1108 hasImeService |= actImeService;
1109 hasWallpaperService |= actWallpaperService;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001110 hasAccessibilityService |= (actAccessibilityService &&
1111 hasBindAccessibilityServicePermission);
1112 hasPrintService |= (actPrintService && hasBindPrintServicePermission);
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001113 hasNotificationListenerService |= actNotificationListenerService &&
1114 hasBindNotificationListenerServicePermission;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001115 hasDreamService |= actDreamService && hasBindDreamServicePermission;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001116 hasOtherServices |= (!actImeService && !actWallpaperService &&
Adam Lesinski94fc9122013-09-30 17:16:09 -07001117 !actAccessibilityService && !actPrintService &&
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001118 !actHostApduService && !actOffHostApduService &&
1119 !actNotificationListenerService);
1120 } else if (withinProvider) {
1121 hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes;
Adam Lesinski282e1812014-01-23 18:17:42 -08001122 }
1123 }
1124 withinIntentFilter = false;
1125 }
1126 continue;
1127 }
1128 if (code != ResXMLTree::START_TAG) {
1129 continue;
1130 }
1131 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001132
1133 const char16_t* ctag16 = tree.getElementName(&len);
1134 if (ctag16 == NULL) {
1135 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
1136 goto bail;
1137 }
1138 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -08001139 //printf("Depth %d, %s\n", depth, tag.string());
1140 if (depth == 1) {
1141 if (tag != "manifest") {
1142 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
1143 goto bail;
1144 }
1145 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -07001146 printf("package: name='%s' ",
1147 ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001148 int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
1149 if (error != "") {
1150 fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
1151 goto bail;
1152 }
1153 if (versionCode > 0) {
1154 printf("versionCode='%d' ", versionCode);
1155 } else {
1156 printf("versionCode='' ");
1157 }
1158 String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
1159 if (error != "") {
1160 fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
1161 goto bail;
1162 }
Adam Lesinski25d35a92014-08-11 09:41:56 -07001163 printf("versionName='%s'",
Maurice Chu2675f762013-10-22 17:33:11 -07001164 ResTable::normalizeForOutput(versionName.string()).string());
Adam Lesinski25d35a92014-08-11 09:41:56 -07001165
1166 String8 splitName = getAttribute(tree, NULL, "split", NULL);
1167 if (!splitName.isEmpty()) {
1168 printf(" split='%s'", ResTable::normalizeForOutput(
1169 splitName.string()).string());
1170 }
1171 printf("\n");
Adam Lesinski282e1812014-01-23 18:17:42 -08001172 } else if (depth == 2) {
1173 withinApplication = false;
1174 if (tag == "application") {
1175 withinApplication = true;
1176
1177 String8 label;
1178 const size_t NL = locales.size();
1179 for (size_t i=0; i<NL; i++) {
1180 const char* localeStr = locales[i].string();
1181 assets.setLocale(localeStr != NULL ? localeStr : "");
1182 String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1183 if (llabel != "") {
1184 if (localeStr == NULL || strlen(localeStr) == 0) {
1185 label = llabel;
Maurice Chu2675f762013-10-22 17:33:11 -07001186 printf("application-label:'%s'\n",
1187 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001188 } else {
1189 if (label == "") {
1190 label = llabel;
1191 }
1192 printf("application-label-%s:'%s'\n", localeStr,
Maurice Chu2675f762013-10-22 17:33:11 -07001193 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001194 }
1195 }
1196 }
1197
1198 ResTable_config tmpConfig = config;
1199 const size_t ND = densities.size();
1200 for (size_t i=0; i<ND; i++) {
1201 tmpConfig.density = densities[i];
1202 assets.setConfiguration(tmpConfig);
1203 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1204 if (icon != "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001205 printf("application-icon-%d:'%s'\n", densities[i],
1206 ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001207 }
1208 }
1209 assets.setConfiguration(config);
1210
1211 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1212 if (error != "") {
1213 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
1214 goto bail;
1215 }
1216 int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
1217 if (error != "") {
1218 fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
1219 goto bail;
1220 }
Maurice Chu2675f762013-10-22 17:33:11 -07001221 printf("application: label='%s' ",
1222 ResTable::normalizeForOutput(label.string()).string());
1223 printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001224 if (testOnly != 0) {
1225 printf("testOnly='%d'\n", testOnly);
1226 }
1227
1228 int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
1229 if (error != "") {
1230 fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
1231 goto bail;
1232 }
1233 if (debuggable != 0) {
1234 printf("application-debuggable\n");
1235 }
1236 } else if (tag == "uses-sdk") {
1237 int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
1238 if (error != "") {
1239 error = "";
1240 String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
1241 if (error != "") {
1242 fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
1243 error.string());
1244 goto bail;
1245 }
1246 if (name == "Donut") targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001247 printf("sdkVersion:'%s'\n",
1248 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001249 } else if (code != -1) {
1250 targetSdk = code;
1251 printf("sdkVersion:'%d'\n", code);
1252 }
1253 code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
1254 if (code != -1) {
1255 printf("maxSdkVersion:'%d'\n", code);
1256 }
1257 code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
1258 if (error != "") {
1259 error = "";
1260 String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
1261 if (error != "") {
1262 fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
1263 error.string());
1264 goto bail;
1265 }
1266 if (name == "Donut" && targetSdk < 4) targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001267 printf("targetSdkVersion:'%s'\n",
1268 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001269 } else if (code != -1) {
1270 if (targetSdk < code) {
1271 targetSdk = code;
1272 }
1273 printf("targetSdkVersion:'%d'\n", code);
1274 }
1275 } else if (tag == "uses-configuration") {
1276 int32_t reqTouchScreen = getIntegerAttribute(tree,
1277 REQ_TOUCH_SCREEN_ATTR, NULL, 0);
1278 int32_t reqKeyboardType = getIntegerAttribute(tree,
1279 REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
1280 int32_t reqHardKeyboard = getIntegerAttribute(tree,
1281 REQ_HARD_KEYBOARD_ATTR, NULL, 0);
1282 int32_t reqNavigation = getIntegerAttribute(tree,
1283 REQ_NAVIGATION_ATTR, NULL, 0);
1284 int32_t reqFiveWayNav = getIntegerAttribute(tree,
1285 REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
1286 printf("uses-configuration:");
1287 if (reqTouchScreen != 0) {
1288 printf(" reqTouchScreen='%d'", reqTouchScreen);
1289 }
1290 if (reqKeyboardType != 0) {
1291 printf(" reqKeyboardType='%d'", reqKeyboardType);
1292 }
1293 if (reqHardKeyboard != 0) {
1294 printf(" reqHardKeyboard='%d'", reqHardKeyboard);
1295 }
1296 if (reqNavigation != 0) {
1297 printf(" reqNavigation='%d'", reqNavigation);
1298 }
1299 if (reqFiveWayNav != 0) {
1300 printf(" reqFiveWayNav='%d'", reqFiveWayNav);
1301 }
1302 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001303 } else if (tag == "supports-input") {
1304 withinSupportsInput = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001305 } else if (tag == "supports-screens") {
1306 smallScreen = getIntegerAttribute(tree,
1307 SMALL_SCREEN_ATTR, NULL, 1);
1308 normalScreen = getIntegerAttribute(tree,
1309 NORMAL_SCREEN_ATTR, NULL, 1);
1310 largeScreen = getIntegerAttribute(tree,
1311 LARGE_SCREEN_ATTR, NULL, 1);
1312 xlargeScreen = getIntegerAttribute(tree,
1313 XLARGE_SCREEN_ATTR, NULL, 1);
1314 anyDensity = getIntegerAttribute(tree,
1315 ANY_DENSITY_ATTR, NULL, 1);
1316 requiresSmallestWidthDp = getIntegerAttribute(tree,
1317 REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
1318 compatibleWidthLimitDp = getIntegerAttribute(tree,
1319 COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1320 largestWidthLimitDp = getIntegerAttribute(tree,
1321 LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
Adam Lesinski2c72b682014-06-24 09:56:01 -07001322 } else if (tag == "feature-group") {
1323 withinFeatureGroup = true;
1324 FeatureGroup group;
1325 group.label = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1326 if (error != "") {
1327 fprintf(stderr, "ERROR getting 'android:label' attribute:"
1328 " %s\n", error.string());
1329 goto bail;
1330 }
1331 featureGroups.add(group);
1332
Adam Lesinski282e1812014-01-23 18:17:42 -08001333 } else if (tag == "uses-feature") {
1334 String8 name = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001335 if (name != "" && error == "") {
1336 int req = getIntegerAttribute(tree,
1337 REQUIRED_ATTR, NULL, 1);
1338
Adam Lesinski2c72b682014-06-24 09:56:01 -07001339 commonFeatures.features.add(name, req);
1340 if (req) {
1341 addParentFeatures(&commonFeatures, name);
Adam Lesinski282e1812014-01-23 18:17:42 -08001342 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001343 } else {
1344 int vers = getIntegerAttribute(tree,
1345 GL_ES_VERSION_ATTR, &error);
1346 if (error == "") {
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001347 if (vers > commonFeatures.openGLESVersion) {
1348 commonFeatures.openGLESVersion = vers;
1349 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001350 }
1351 }
1352 } else if (tag == "uses-permission") {
1353 String8 name = getAttribute(tree, NAME_ATTR, &error);
1354 if (name != "" && error == "") {
1355 if (name == "android.permission.CAMERA") {
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001356 addImpliedFeature(&impliedFeatures, "android.hardware.camera",
Adam Lesinski2c72b682014-06-24 09:56:01 -07001357 String8::format("requested %s permission", name.string())
1358 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001359 } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001360 addImpliedFeature(&impliedFeatures, "android.hardware.location.gps",
1361 String8::format("requested %s permission", name.string())
1362 .string());
1363 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1364 String8::format("requested %s permission", name.string())
1365 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001366 } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
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.ACCESS_COARSE_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001371 addImpliedFeature(&impliedFeatures, "android.hardware.location.network",
1372 String8::format("requested %s permission", name.string())
1373 .string());
1374 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1375 String8::format("requested %s permission", name.string())
1376 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001377 } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
1378 name == "android.permission.INSTALL_LOCATION_PROVIDER") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001379 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1380 String8::format("requested %s permission", name.string())
1381 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001382 } else if (name == "android.permission.BLUETOOTH" ||
1383 name == "android.permission.BLUETOOTH_ADMIN") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001384 if (targetSdk > 4) {
1385 addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
1386 String8::format("requested %s permission", name.string())
1387 .string());
1388 addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
1389 "targetSdkVersion > 4");
1390 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001391 } else if (name == "android.permission.RECORD_AUDIO") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001392 addImpliedFeature(&impliedFeatures, "android.hardware.microphone",
1393 String8::format("requested %s permission", name.string())
1394 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001395 } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
1396 name == "android.permission.CHANGE_WIFI_STATE" ||
1397 name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001398 addImpliedFeature(&impliedFeatures, "android.hardware.wifi",
1399 String8::format("requested %s permission", name.string())
1400 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001401 } else if (name == "android.permission.CALL_PHONE" ||
1402 name == "android.permission.CALL_PRIVILEGED" ||
1403 name == "android.permission.MODIFY_PHONE_STATE" ||
1404 name == "android.permission.PROCESS_OUTGOING_CALLS" ||
1405 name == "android.permission.READ_SMS" ||
1406 name == "android.permission.RECEIVE_SMS" ||
1407 name == "android.permission.RECEIVE_MMS" ||
1408 name == "android.permission.RECEIVE_WAP_PUSH" ||
1409 name == "android.permission.SEND_SMS" ||
1410 name == "android.permission.WRITE_APN_SETTINGS" ||
1411 name == "android.permission.WRITE_SMS") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001412 addImpliedFeature(&impliedFeatures, "android.hardware.telephony",
1413 String8("requested a telephony permission").string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001414 } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
1415 hasWriteExternalStoragePermission = true;
1416 } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
1417 hasReadExternalStoragePermission = true;
1418 } else if (name == "android.permission.READ_PHONE_STATE") {
1419 hasReadPhoneStatePermission = true;
1420 } else if (name == "android.permission.READ_CONTACTS") {
1421 hasReadContactsPermission = true;
1422 } else if (name == "android.permission.WRITE_CONTACTS") {
1423 hasWriteContactsPermission = true;
1424 } else if (name == "android.permission.READ_CALL_LOG") {
1425 hasReadCallLogPermission = true;
1426 } else if (name == "android.permission.WRITE_CALL_LOG") {
1427 hasWriteCallLogPermission = true;
1428 }
Adam Lesinski58f1f362013-11-12 12:59:08 -08001429
1430 printUsesPermission(name,
1431 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
1432 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
1433 } else {
Adam Lesinski282e1812014-01-23 18:17:42 -08001434 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1435 error.string());
1436 goto bail;
1437 }
1438 } else if (tag == "uses-package") {
1439 String8 name = getAttribute(tree, NAME_ATTR, &error);
1440 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001441 printf("uses-package:'%s'\n",
1442 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001443 } else {
1444 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1445 error.string());
1446 goto bail;
1447 }
1448 } else if (tag == "original-package") {
1449 String8 name = getAttribute(tree, NAME_ATTR, &error);
1450 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001451 printf("original-package:'%s'\n",
1452 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001453 } else {
1454 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1455 error.string());
1456 goto bail;
1457 }
1458 } else if (tag == "supports-gl-texture") {
1459 String8 name = getAttribute(tree, NAME_ATTR, &error);
1460 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001461 printf("supports-gl-texture:'%s'\n",
1462 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001463 } else {
1464 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1465 error.string());
1466 goto bail;
1467 }
1468 } else if (tag == "compatible-screens") {
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001469 printCompatibleScreens(tree, &error);
1470 if (error != "") {
1471 fprintf(stderr, "ERROR getting compatible screens: %s\n",
1472 error.string());
1473 goto bail;
1474 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001475 depth--;
1476 } else if (tag == "package-verifier") {
1477 String8 name = getAttribute(tree, NAME_ATTR, &error);
1478 if (name != "" && error == "") {
1479 String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
1480 if (publicKey != "" && error == "") {
1481 printf("package-verifier: name='%s' publicKey='%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001482 ResTable::normalizeForOutput(name.string()).string(),
1483 ResTable::normalizeForOutput(publicKey.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001484 }
1485 }
1486 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001487 } else if (depth == 3) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001488 withinActivity = false;
1489 withinReceiver = false;
1490 withinService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001491 withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001492 hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001493 hasMetaHostPaymentCategory = false;
1494 hasMetaOffHostPaymentCategory = false;
1495 hasBindDeviceAdminPermission = false;
1496 hasBindInputMethodPermission = false;
1497 hasBindAccessibilityServicePermission = false;
1498 hasBindPrintServicePermission = false;
1499 hasBindNfcServicePermission = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001500 hasRequiredSafAttributes = false;
1501 hasBindNotificationListenerServicePermission = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001502 hasBindDreamServicePermission = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001503 if (withinApplication) {
1504 if(tag == "activity") {
1505 withinActivity = true;
1506 activityName = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001507 if (error != "") {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001508 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1509 error.string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001510 goto bail;
1511 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001512
Michael Wrightec4fdec2013-09-06 16:50:52 -07001513 activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1514 if (error != "") {
1515 fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
1516 error.string());
1517 goto bail;
1518 }
1519
1520 activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1521 if (error != "") {
1522 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
1523 error.string());
1524 goto bail;
1525 }
1526
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001527 activityBanner = getResolvedAttribute(&res, tree, BANNER_ATTR, &error);
1528 if (error != "") {
1529 fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
1530 error.string());
1531 goto bail;
1532 }
1533
Michael Wrightec4fdec2013-09-06 16:50:52 -07001534 int32_t orien = getResolvedIntegerAttribute(&res, tree,
1535 SCREEN_ORIENTATION_ATTR, &error);
1536 if (error == "") {
1537 if (orien == 0 || orien == 6 || orien == 8) {
1538 // Requests landscape, sensorLandscape, or reverseLandscape.
Adam Lesinski2c72b682014-06-24 09:56:01 -07001539 addImpliedFeature(&impliedFeatures, "android.hardware.screen.landscape",
1540 "one or more activities have specified a landscape orientation");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001541 } else if (orien == 1 || orien == 7 || orien == 9) {
1542 // Requests portrait, sensorPortrait, or reversePortrait.
Adam Lesinski2c72b682014-06-24 09:56:01 -07001543 addImpliedFeature(&impliedFeatures, "android.hardware.screen.portrait",
1544 "one or more activities have specified a portrait orientation");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001545 }
1546 }
1547 } else if (tag == "uses-library") {
1548 String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
1549 if (error != "") {
1550 fprintf(stderr,
1551 "ERROR getting 'android:name' attribute for uses-library"
1552 " %s\n", error.string());
1553 goto bail;
1554 }
1555 int req = getIntegerAttribute(tree,
1556 REQUIRED_ATTR, NULL, 1);
1557 printf("uses-library%s:'%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001558 req ? "" : "-not-required", ResTable::normalizeForOutput(
1559 libraryName.string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001560 } else if (tag == "receiver") {
1561 withinReceiver = true;
1562 receiverName = getAttribute(tree, NAME_ATTR, &error);
1563
1564 if (error != "") {
1565 fprintf(stderr,
1566 "ERROR getting 'android:name' attribute for receiver:"
1567 " %s\n", error.string());
1568 goto bail;
1569 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001570
1571 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1572 if (error == "") {
1573 if (permission == "android.permission.BIND_DEVICE_ADMIN") {
1574 hasBindDeviceAdminPermission = true;
1575 }
1576 } else {
1577 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1578 " receiver '%s': %s\n", receiverName.string(), error.string());
1579 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001580 } else if (tag == "service") {
1581 withinService = true;
1582 serviceName = getAttribute(tree, NAME_ATTR, &error);
1583
1584 if (error != "") {
1585 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1586 "service:%s\n", error.string());
1587 goto bail;
1588 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001589
1590 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1591 if (error == "") {
1592 if (permission == "android.permission.BIND_INPUT_METHOD") {
1593 hasBindInputMethodPermission = true;
1594 } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
1595 hasBindAccessibilityServicePermission = true;
1596 } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
1597 hasBindPrintServicePermission = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001598 } else if (permission == "android.permission.BIND_NFC_SERVICE") {
1599 hasBindNfcServicePermission = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001600 } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
1601 hasBindNotificationListenerServicePermission = true;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001602 } else if (permission == "android.permission.BIND_DREAM_SERVICE") {
1603 hasBindDreamServicePermission = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001604 }
1605 } else {
1606 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1607 " service '%s': %s\n", serviceName.string(), error.string());
1608 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001609 } else if (tag == "provider") {
1610 withinProvider = true;
1611
1612 bool exported = getResolvedIntegerAttribute(&res, tree, EXPORTED_ATTR, &error);
1613 if (error != "") {
1614 fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:"
1615 " %s\n", error.string());
1616 goto bail;
1617 }
1618
1619 bool grantUriPermissions = getResolvedIntegerAttribute(&res, tree,
1620 GRANT_URI_PERMISSIONS_ATTR, &error);
1621 if (error != "") {
1622 fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:"
1623 " %s\n", error.string());
1624 goto bail;
1625 }
1626
1627 String8 permission = getResolvedAttribute(&res, tree, PERMISSION_ATTR, &error);
1628 if (error != "") {
1629 fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:"
1630 " %s\n", error.string());
1631 goto bail;
1632 }
1633
1634 hasRequiredSafAttributes |= exported && grantUriPermissions &&
1635 permission == "android.permission.MANAGE_DOCUMENTS";
1636
Michael Wrightec4fdec2013-09-06 16:50:52 -07001637 } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
Adam Lesinskib71adb62014-05-15 14:14:41 -07001638 String8 metaDataName = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001639 if (error != "") {
1640 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1641 "meta-data:%s\n", error.string());
1642 goto bail;
1643 }
Maurice Chu2675f762013-10-22 17:33:11 -07001644 printf("meta-data: name='%s' ",
1645 ResTable::normalizeForOutput(metaDataName.string()).string());
Maurice Chu76327312013-10-16 18:28:46 -07001646 printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"),
1647 &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001648 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001649 // Try looking for a RESOURCE_ATTR
1650 error = "";
1651 printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR,
1652 String8("resource"), &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001653 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001654 fprintf(stderr, "ERROR getting 'android:value' or "
1655 "'android:resource' attribute for "
1656 "meta-data:%s\n", error.string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001657 goto bail;
1658 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001659 }
Maurice Chu76327312013-10-16 18:28:46 -07001660 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001661 } else if (withinSupportsInput && tag == "input-type") {
1662 String8 name = getAttribute(tree, NAME_ATTR, &error);
1663 if (name != "" && error == "") {
1664 supportedInput.add(name);
1665 } else {
1666 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1667 error.string());
1668 goto bail;
1669 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001670 }
Adam Lesinski2c72b682014-06-24 09:56:01 -07001671 } else if (withinFeatureGroup && tag == "uses-feature") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001672 FeatureGroup& top = featureGroups.editTop();
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001673
1674 String8 name = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
1675 if (name != "" && error == "") {
1676 int required = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
1677 top.features.add(name, required);
1678 if (required) {
1679 addParentFeatures(&top, name);
1680 }
1681
1682 } else {
1683 int vers = getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error);
1684 if (error == "") {
1685 if (vers > top.openGLESVersion) {
1686 top.openGLESVersion = vers;
1687 }
1688 }
Adam Lesinski2c72b682014-06-24 09:56:01 -07001689 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001690 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001691 } else if (depth == 4) {
1692 if (tag == "intent-filter") {
1693 hasIntentFilter = true;
1694 withinIntentFilter = true;
1695 actMainActivity = false;
1696 actWidgetReceivers = false;
1697 actImeService = false;
1698 actWallpaperService = false;
1699 actAccessibilityService = false;
1700 actPrintService = false;
1701 actDeviceAdminEnabled = false;
1702 actHostApduService = false;
1703 actOffHostApduService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001704 actDocumentsProvider = false;
1705 actNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001706 actDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001707 actCamera = false;
1708 actCameraSecure = false;
1709 catLauncher = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001710 } else if (withinService && tag == "meta-data") {
1711 String8 name = getAttribute(tree, NAME_ATTR, &error);
1712 if (error != "") {
1713 fprintf(stderr, "ERROR getting 'android:name' attribute for"
1714 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1715 goto bail;
1716 }
1717
1718 if (name == "android.nfc.cardemulation.host_apdu_service" ||
1719 name == "android.nfc.cardemulation.off_host_apdu_service") {
1720 bool offHost = true;
1721 if (name == "android.nfc.cardemulation.host_apdu_service") {
1722 offHost = false;
1723 }
1724
1725 String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
1726 if (error != "") {
1727 fprintf(stderr, "ERROR getting 'android:resource' attribute for"
1728 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1729 goto bail;
1730 }
1731
1732 Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
1733 offHost, &error);
1734 if (error != "") {
1735 fprintf(stderr, "ERROR getting AID category for service '%s'\n",
1736 serviceName.string());
1737 goto bail;
1738 }
1739
1740 const size_t catLen = categories.size();
1741 for (size_t i = 0; i < catLen; i++) {
1742 bool paymentCategory = (categories[i] == "payment");
1743 if (offHost) {
1744 hasMetaOffHostPaymentCategory |= paymentCategory;
1745 } else {
1746 hasMetaHostPaymentCategory |= paymentCategory;
1747 }
1748 }
1749 }
1750 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001751 } else if ((depth == 5) && withinIntentFilter) {
1752 String8 action;
1753 if (tag == "action") {
1754 action = getAttribute(tree, NAME_ATTR, &error);
1755 if (error != "") {
1756 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1757 error.string());
1758 goto bail;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001759 }
1760
Adam Lesinskia5018c92013-09-30 16:23:15 -07001761 if (withinActivity) {
1762 if (action == "android.intent.action.MAIN") {
1763 isMainActivity = true;
1764 actMainActivity = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001765 } else if (action == "android.media.action.STILL_IMAGE_CAMERA" ||
1766 action == "android.media.action.VIDEO_CAMERA") {
1767 actCamera = true;
1768 } else if (action == "android.media.action.STILL_IMAGE_CAMERA_SECURE") {
1769 actCameraSecure = true;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001770 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001771 } else if (withinReceiver) {
1772 if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
1773 actWidgetReceivers = true;
1774 } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
1775 actDeviceAdminEnabled = true;
1776 }
1777 } else if (withinService) {
1778 if (action == "android.view.InputMethod") {
1779 actImeService = true;
1780 } else if (action == "android.service.wallpaper.WallpaperService") {
1781 actWallpaperService = true;
1782 } else if (action == "android.accessibilityservice.AccessibilityService") {
1783 actAccessibilityService = true;
1784 } else if (action == "android.printservice.PrintService") {
1785 actPrintService = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001786 } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
1787 actHostApduService = true;
1788 } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
1789 actOffHostApduService = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001790 } else if (action == "android.service.notification.NotificationListenerService") {
1791 actNotificationListenerService = true;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001792 } else if (action == "android.service.dreams.DreamService") {
1793 actDreamService = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001794 }
1795 } else if (withinProvider) {
1796 if (action == "android.content.action.DOCUMENTS_PROVIDER") {
1797 actDocumentsProvider = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001798 }
1799 }
1800 if (action == "android.intent.action.SEARCH") {
1801 isSearchable = true;
1802 }
1803 }
1804
1805 if (tag == "category") {
1806 String8 category = getAttribute(tree, NAME_ATTR, &error);
1807 if (error != "") {
1808 fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
1809 error.string());
1810 goto bail;
1811 }
1812 if (withinActivity) {
1813 if (category == "android.intent.category.LAUNCHER") {
1814 isLauncherActivity = true;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001815 } else if (category == "android.intent.category.LEANBACK_LAUNCHER") {
1816 isLeanbackLauncherActivity = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001817 } else if (category == "android.intent.category.HOME") {
1818 catLauncher = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001819 }
1820 }
1821 }
1822 }
1823 }
1824
1825 // Pre-1.6 implicitly granted permission compatibility logic
1826 if (targetSdk < 4) {
1827 if (!hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001828 printUsesPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"));
1829 printUsesImpliedPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"),
1830 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001831 hasWriteExternalStoragePermission = true;
1832 }
1833 if (!hasReadPhoneStatePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001834 printUsesPermission(String8("android.permission.READ_PHONE_STATE"));
1835 printUsesImpliedPermission(String8("android.permission.READ_PHONE_STATE"),
1836 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001837 }
1838 }
1839
1840 // If the application has requested WRITE_EXTERNAL_STORAGE, we will
1841 // force them to always take READ_EXTERNAL_STORAGE as well. We always
1842 // do this (regardless of target API version) because we can't have
1843 // an app with write permission but not read permission.
1844 if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001845 printUsesPermission(String8("android.permission.READ_EXTERNAL_STORAGE"));
1846 printUsesImpliedPermission(String8("android.permission.READ_EXTERNAL_STORAGE"),
1847 String8("requested WRITE_EXTERNAL_STORAGE"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001848 }
1849
1850 // Pre-JellyBean call log permission compatibility.
1851 if (targetSdk < 16) {
1852 if (!hasReadCallLogPermission && hasReadContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001853 printUsesPermission(String8("android.permission.READ_CALL_LOG"));
1854 printUsesImpliedPermission(String8("android.permission.READ_CALL_LOG"),
1855 String8("targetSdkVersion < 16 and requested READ_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001856 }
1857 if (!hasWriteCallLogPermission && hasWriteContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001858 printUsesPermission(String8("android.permission.WRITE_CALL_LOG"));
1859 printUsesImpliedPermission(String8("android.permission.WRITE_CALL_LOG"),
1860 String8("targetSdkVersion < 16 and requested WRITE_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001861 }
1862 }
1863
Adam Lesinski2c72b682014-06-24 09:56:01 -07001864 addImpliedFeature(&impliedFeatures, "android.hardware.touchscreen",
1865 "default feature for all apps");
1866
1867 const size_t numFeatureGroups = featureGroups.size();
1868 if (numFeatureGroups == 0) {
1869 // If no <feature-group> tags were defined, apply auto-implied features.
1870 printFeatureGroup(commonFeatures, &impliedFeatures);
1871
1872 } else {
1873 // <feature-group> tags are defined, so we ignore implied features and
1874 for (size_t i = 0; i < numFeatureGroups; i++) {
1875 FeatureGroup& grp = featureGroups.editItemAt(i);
1876
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001877 if (commonFeatures.openGLESVersion > grp.openGLESVersion) {
1878 grp.openGLESVersion = commonFeatures.openGLESVersion;
1879 }
1880
Adam Lesinski2c72b682014-06-24 09:56:01 -07001881 // Merge the features defined in the top level (not inside a <feature-group>)
1882 // with this feature group.
1883 const size_t numCommonFeatures = commonFeatures.features.size();
1884 for (size_t j = 0; j < numCommonFeatures; j++) {
1885 if (grp.features.indexOfKey(commonFeatures.features.keyAt(j)) < 0) {
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001886 grp.features.add(commonFeatures.features.keyAt(j),
1887 commonFeatures.features[j]);
Adam Lesinski2c72b682014-06-24 09:56:01 -07001888 }
1889 }
1890
1891 if (!grp.features.isEmpty()) {
1892 printFeatureGroup(grp);
Adam Lesinski282e1812014-01-23 18:17:42 -08001893 }
1894 }
1895 }
1896
Adam Lesinski282e1812014-01-23 18:17:42 -08001897
Adam Lesinski282e1812014-01-23 18:17:42 -08001898 if (hasWidgetReceivers) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001899 printComponentPresence("app-widget");
Adam Lesinski282e1812014-01-23 18:17:42 -08001900 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001901 if (hasDeviceAdminReceiver) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001902 printComponentPresence("device-admin");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001903 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001904 if (hasImeService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001905 printComponentPresence("ime");
Adam Lesinski282e1812014-01-23 18:17:42 -08001906 }
1907 if (hasWallpaperService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001908 printComponentPresence("wallpaper");
Adam Lesinski282e1812014-01-23 18:17:42 -08001909 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001910 if (hasAccessibilityService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001911 printComponentPresence("accessibility");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001912 }
1913 if (hasPrintService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001914 printComponentPresence("print-service");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001915 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001916 if (hasPaymentService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001917 printComponentPresence("payment");
1918 }
1919 if (isSearchable) {
1920 printComponentPresence("search");
1921 }
1922 if (hasDocumentsProvider) {
1923 printComponentPresence("document-provider");
1924 }
1925 if (hasLauncher) {
1926 printComponentPresence("launcher");
1927 }
1928 if (hasNotificationListenerService) {
1929 printComponentPresence("notification-listener");
1930 }
John Spurlockeb8d1be2014-06-25 17:46:15 -04001931 if (hasDreamService) {
1932 printComponentPresence("dream");
1933 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001934 if (hasCameraActivity) {
1935 printComponentPresence("camera");
1936 }
1937 if (hasCameraSecureActivity) {
1938 printComponentPresence("camera-secure");
1939 }
1940
1941 if (hasMainActivity) {
1942 printf("main\n");
Adam Lesinski94fc9122013-09-30 17:16:09 -07001943 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001944 if (hasOtherActivities) {
1945 printf("other-activities\n");
1946 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001947 if (hasOtherReceivers) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001948 printf("other-receivers\n");
1949 }
1950 if (hasOtherServices) {
1951 printf("other-services\n");
1952 }
1953
1954 // For modern apps, if screen size buckets haven't been specified
1955 // but the new width ranges have, then infer the buckets from them.
1956 if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0
1957 && requiresSmallestWidthDp > 0) {
1958 int compatWidth = compatibleWidthLimitDp;
1959 if (compatWidth <= 0) {
1960 compatWidth = requiresSmallestWidthDp;
1961 }
1962 if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) {
1963 smallScreen = -1;
1964 } else {
1965 smallScreen = 0;
1966 }
1967 if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) {
1968 normalScreen = -1;
1969 } else {
1970 normalScreen = 0;
1971 }
1972 if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) {
1973 largeScreen = -1;
1974 } else {
1975 largeScreen = 0;
1976 }
1977 if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) {
1978 xlargeScreen = -1;
1979 } else {
1980 xlargeScreen = 0;
1981 }
1982 }
1983
1984 // Determine default values for any unspecified screen sizes,
1985 // based on the target SDK of the package. As of 4 (donut)
1986 // the screen size support was introduced, so all default to
1987 // enabled.
1988 if (smallScreen > 0) {
1989 smallScreen = targetSdk >= 4 ? -1 : 0;
1990 }
1991 if (normalScreen > 0) {
1992 normalScreen = -1;
1993 }
1994 if (largeScreen > 0) {
1995 largeScreen = targetSdk >= 4 ? -1 : 0;
1996 }
1997 if (xlargeScreen > 0) {
1998 // Introduced in Gingerbread.
1999 xlargeScreen = targetSdk >= 9 ? -1 : 0;
2000 }
2001 if (anyDensity > 0) {
2002 anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0
2003 || compatibleWidthLimitDp > 0) ? -1 : 0;
2004 }
2005 printf("supports-screens:");
2006 if (smallScreen != 0) {
2007 printf(" 'small'");
2008 }
2009 if (normalScreen != 0) {
2010 printf(" 'normal'");
2011 }
2012 if (largeScreen != 0) {
2013 printf(" 'large'");
2014 }
2015 if (xlargeScreen != 0) {
2016 printf(" 'xlarge'");
2017 }
2018 printf("\n");
2019 printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
2020 if (requiresSmallestWidthDp > 0) {
2021 printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp);
2022 }
2023 if (compatibleWidthLimitDp > 0) {
2024 printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp);
2025 }
2026 if (largestWidthLimitDp > 0) {
2027 printf("largest-width-limit:'%d'\n", largestWidthLimitDp);
2028 }
2029
2030 printf("locales:");
2031 const size_t NL = locales.size();
2032 for (size_t i=0; i<NL; i++) {
2033 const char* localeStr = locales[i].string();
2034 if (localeStr == NULL || strlen(localeStr) == 0) {
2035 localeStr = "--_--";
2036 }
2037 printf(" '%s'", localeStr);
2038 }
2039 printf("\n");
2040
2041 printf("densities:");
2042 const size_t ND = densities.size();
2043 for (size_t i=0; i<ND; i++) {
2044 printf(" '%d'", densities[i]);
2045 }
2046 printf("\n");
2047
2048 AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
2049 if (dir != NULL) {
2050 if (dir->getFileCount() > 0) {
2051 printf("native-code:");
2052 for (size_t i=0; i<dir->getFileCount(); i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07002053 printf(" '%s'", ResTable::normalizeForOutput(
2054 dir->getFileName(i).string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002055 }
2056 printf("\n");
2057 }
2058 delete dir;
2059 }
2060 } else if (strcmp("badger", option) == 0) {
2061 printf("%s", CONSOLE_DATA);
2062 } else if (strcmp("configurations", option) == 0) {
2063 Vector<ResTable_config> configs;
2064 res.getConfigurations(&configs);
2065 const size_t N = configs.size();
2066 for (size_t i=0; i<N; i++) {
2067 printf("%s\n", configs[i].toString().string());
2068 }
2069 } else {
2070 fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
2071 goto bail;
2072 }
2073 }
2074
2075 result = NO_ERROR;
2076
2077bail:
2078 if (asset) {
2079 delete asset;
2080 }
2081 return (result != NO_ERROR);
2082}
2083
2084
2085/*
2086 * Handle the "add" command, which wants to add files to a new or
2087 * pre-existing archive.
2088 */
2089int doAdd(Bundle* bundle)
2090{
2091 ZipFile* zip = NULL;
2092 status_t result = UNKNOWN_ERROR;
2093 const char* zipFileName;
2094
2095 if (bundle->getUpdate()) {
2096 /* avoid confusion */
2097 fprintf(stderr, "ERROR: can't use '-u' with add\n");
2098 goto bail;
2099 }
2100
2101 if (bundle->getFileSpecCount() < 1) {
2102 fprintf(stderr, "ERROR: must specify zip file name\n");
2103 goto bail;
2104 }
2105 zipFileName = bundle->getFileSpecEntry(0);
2106
2107 if (bundle->getFileSpecCount() < 2) {
2108 fprintf(stderr, "NOTE: nothing to do\n");
2109 goto bail;
2110 }
2111
2112 zip = openReadWrite(zipFileName, true);
2113 if (zip == NULL) {
2114 fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
2115 goto bail;
2116 }
2117
2118 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2119 const char* fileName = bundle->getFileSpecEntry(i);
2120
2121 if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
2122 printf(" '%s'... (from gzip)\n", fileName);
2123 result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
2124 } else {
2125 if (bundle->getJunkPath()) {
2126 String8 storageName = String8(fileName).getPathLeaf();
Maurice Chu2675f762013-10-22 17:33:11 -07002127 printf(" '%s' as '%s'...\n", fileName,
2128 ResTable::normalizeForOutput(storageName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002129 result = zip->add(fileName, storageName.string(),
2130 bundle->getCompressionMethod(), NULL);
2131 } else {
2132 printf(" '%s'...\n", fileName);
2133 result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
2134 }
2135 }
2136 if (result != NO_ERROR) {
2137 fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
2138 if (result == NAME_NOT_FOUND) {
2139 fprintf(stderr, ": file not found\n");
2140 } else if (result == ALREADY_EXISTS) {
2141 fprintf(stderr, ": already exists in archive\n");
2142 } else {
2143 fprintf(stderr, "\n");
2144 }
2145 goto bail;
2146 }
2147 }
2148
2149 result = NO_ERROR;
2150
2151bail:
2152 delete zip;
2153 return (result != NO_ERROR);
2154}
2155
2156
2157/*
2158 * Delete files from an existing archive.
2159 */
2160int doRemove(Bundle* bundle)
2161{
2162 ZipFile* zip = NULL;
2163 status_t result = UNKNOWN_ERROR;
2164 const char* zipFileName;
2165
2166 if (bundle->getFileSpecCount() < 1) {
2167 fprintf(stderr, "ERROR: must specify zip file name\n");
2168 goto bail;
2169 }
2170 zipFileName = bundle->getFileSpecEntry(0);
2171
2172 if (bundle->getFileSpecCount() < 2) {
2173 fprintf(stderr, "NOTE: nothing to do\n");
2174 goto bail;
2175 }
2176
2177 zip = openReadWrite(zipFileName, false);
2178 if (zip == NULL) {
2179 fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
2180 zipFileName);
2181 goto bail;
2182 }
2183
2184 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2185 const char* fileName = bundle->getFileSpecEntry(i);
2186 ZipEntry* entry;
2187
2188 entry = zip->getEntryByName(fileName);
2189 if (entry == NULL) {
2190 printf(" '%s' NOT FOUND\n", fileName);
2191 continue;
2192 }
2193
2194 result = zip->remove(entry);
2195
2196 if (result != NO_ERROR) {
2197 fprintf(stderr, "Unable to delete '%s' from '%s'\n",
2198 bundle->getFileSpecEntry(i), zipFileName);
2199 goto bail;
2200 }
2201 }
2202
2203 /* update the archive */
2204 zip->flush();
2205
2206bail:
2207 delete zip;
2208 return (result != NO_ERROR);
2209}
2210
Adam Lesinski3921e872014-05-13 10:56:25 -07002211static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder, bool ignoreConfig=false) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002212 const size_t numDirs = dir->getDirs().size();
2213 for (size_t i = 0; i < numDirs; i++) {
Adam Lesinski3921e872014-05-13 10:56:25 -07002214 bool ignore = ignoreConfig;
2215 const sp<AaptDir>& subDir = dir->getDirs().valueAt(i);
2216 const char* dirStr = subDir->getLeaf().string();
2217 if (!ignore && strstr(dirStr, "mipmap") == dirStr) {
2218 ignore = true;
2219 }
2220 status_t err = addResourcesToBuilder(subDir, builder, ignore);
Adam Lesinskifab50872014-04-16 14:40:42 -07002221 if (err != NO_ERROR) {
2222 return err;
2223 }
2224 }
2225
2226 const size_t numFiles = dir->getFiles().size();
2227 for (size_t i = 0; i < numFiles; i++) {
2228 sp<AaptGroup> gp = dir->getFiles().valueAt(i);
2229 const size_t numConfigs = gp->getFiles().size();
2230 for (size_t j = 0; j < numConfigs; j++) {
Adam Lesinski3921e872014-05-13 10:56:25 -07002231 status_t err = NO_ERROR;
2232 if (ignoreConfig) {
2233 err = builder->getBaseSplit()->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2234 } else {
2235 err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2236 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002237 if (err != NO_ERROR) {
2238 fprintf(stderr, "Failed to add %s (%s) to builder.\n",
2239 gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string());
2240 return err;
2241 }
2242 }
2243 }
2244 return NO_ERROR;
2245}
2246
2247static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) {
2248 if (split->isBase()) {
2249 return original;
2250 }
2251
2252 String8 ext(original.getPathExtension());
2253 if (ext == String8(".apk")) {
2254 return String8::format("%s_%s%s",
2255 original.getBasePath().string(),
2256 split->getDirectorySafeName().string(),
2257 ext.string());
2258 }
2259
2260 return String8::format("%s_%s", original.string(),
2261 split->getDirectorySafeName().string());
2262}
Adam Lesinski282e1812014-01-23 18:17:42 -08002263
2264/*
2265 * Package up an asset directory and associated application files.
2266 */
2267int doPackage(Bundle* bundle)
2268{
2269 const char* outputAPKFile;
2270 int retVal = 1;
2271 status_t err;
2272 sp<AaptAssets> assets;
2273 int N;
2274 FILE* fp;
2275 String8 dependencyFile;
Adam Lesinskifab50872014-04-16 14:40:42 -07002276 sp<ApkBuilder> builder;
Adam Lesinski282e1812014-01-23 18:17:42 -08002277
Anton Krumina2ef5c02014-03-12 14:46:44 -07002278 // -c en_XA or/and ar_XB means do pseudolocalization
Adam Lesinskifab50872014-04-16 14:40:42 -07002279 sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
2280 err = configFilter->parse(bundle->getConfigurations());
Adam Lesinski282e1812014-01-23 18:17:42 -08002281 if (err != NO_ERROR) {
2282 goto bail;
2283 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002284 if (configFilter->containsPseudo()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002285 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
2286 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002287 if (configFilter->containsPseudoBidi()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002288 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
Adam Lesinski282e1812014-01-23 18:17:42 -08002289 }
2290
2291 N = bundle->getFileSpecCount();
2292 if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
Adam Lesinski09384302014-01-22 16:07:42 -08002293 && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002294 fprintf(stderr, "ERROR: no input files\n");
2295 goto bail;
2296 }
2297
2298 outputAPKFile = bundle->getOutputAPKFile();
2299
2300 // Make sure the filenames provided exist and are of the appropriate type.
2301 if (outputAPKFile) {
2302 FileType type;
2303 type = getFileType(outputAPKFile);
2304 if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
2305 fprintf(stderr,
2306 "ERROR: output file '%s' exists but is not regular file\n",
2307 outputAPKFile);
2308 goto bail;
2309 }
2310 }
2311
2312 // Load the assets.
2313 assets = new AaptAssets();
2314
2315 // Set up the resource gathering in assets if we're going to generate
2316 // dependency files. Every time we encounter a resource while slurping
2317 // the tree, we'll add it to these stores so we have full resource paths
2318 // to write to a dependency file.
2319 if (bundle->getGenDependencies()) {
2320 sp<FilePathStore> resPathStore = new FilePathStore;
2321 assets->setFullResPaths(resPathStore);
2322 sp<FilePathStore> assetPathStore = new FilePathStore;
2323 assets->setFullAssetPaths(assetPathStore);
2324 }
2325
2326 err = assets->slurpFromArgs(bundle);
2327 if (err < 0) {
2328 goto bail;
2329 }
2330
2331 if (bundle->getVerbose()) {
2332 assets->print(String8());
2333 }
2334
Adam Lesinskifab50872014-04-16 14:40:42 -07002335 // Create the ApkBuilder, which will collect the compiled files
2336 // to write to the final APK (or sets of APKs if we are building
2337 // a Split APK.
2338 builder = new ApkBuilder(configFilter);
2339
2340 // If we are generating a Split APK, find out which configurations to split on.
2341 if (bundle->getSplitConfigurations().size() > 0) {
2342 const Vector<String8>& splitStrs = bundle->getSplitConfigurations();
2343 const size_t numSplits = splitStrs.size();
2344 for (size_t i = 0; i < numSplits; i++) {
2345 std::set<ConfigDescription> configs;
2346 if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {
2347 fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());
2348 goto bail;
2349 }
2350
2351 err = builder->createSplitForConfigs(configs);
2352 if (err != NO_ERROR) {
2353 goto bail;
2354 }
2355 }
2356 }
2357
Adam Lesinski282e1812014-01-23 18:17:42 -08002358 // If they asked for any fileAs that need to be compiled, do so.
2359 if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002360 err = buildResources(bundle, assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002361 if (err != 0) {
2362 goto bail;
2363 }
2364 }
2365
2366 // At this point we've read everything and processed everything. From here
2367 // on out it's just writing output files.
2368 if (SourcePos::hasErrors()) {
2369 goto bail;
2370 }
2371
2372 // Update symbols with information about which ones are needed as Java symbols.
2373 assets->applyJavaSymbols();
2374 if (SourcePos::hasErrors()) {
2375 goto bail;
2376 }
2377
2378 // If we've been asked to generate a dependency file, do that here
2379 if (bundle->getGenDependencies()) {
2380 // If this is the packaging step, generate the dependency file next to
2381 // the output apk (e.g. bin/resources.ap_.d)
2382 if (outputAPKFile) {
2383 dependencyFile = String8(outputAPKFile);
2384 // Add the .d extension to the dependency file.
2385 dependencyFile.append(".d");
2386 } else {
2387 // Else if this is the R.java dependency generation step,
2388 // generate the dependency file in the R.java package subdirectory
2389 // e.g. gen/com/foo/app/R.java.d
2390 dependencyFile = String8(bundle->getRClassDir());
2391 dependencyFile.appendPath("R.java.d");
2392 }
2393 // Make sure we have a clean dependency file to start with
2394 fp = fopen(dependencyFile, "w");
2395 fclose(fp);
2396 }
2397
2398 // Write out R.java constants
2399 if (!assets->havePrivateSymbols()) {
2400 if (bundle->getCustomPackage() == NULL) {
2401 // Write the R.java file into the appropriate class directory
2402 // e.g. gen/com/foo/app/R.java
2403 err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
2404 } else {
2405 const String8 customPkg(bundle->getCustomPackage());
2406 err = writeResourceSymbols(bundle, assets, customPkg, true);
2407 }
2408 if (err < 0) {
2409 goto bail;
2410 }
2411 // If we have library files, we're going to write our R.java file into
2412 // the appropriate class directory for those libraries as well.
2413 // e.g. gen/com/foo/app/lib/R.java
2414 if (bundle->getExtraPackages() != NULL) {
2415 // Split on colon
2416 String8 libs(bundle->getExtraPackages());
2417 char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
2418 while (packageString != NULL) {
2419 // Write the R.java file out with the correct package name
2420 err = writeResourceSymbols(bundle, assets, String8(packageString), true);
2421 if (err < 0) {
2422 goto bail;
2423 }
2424 packageString = strtok(NULL, ":");
2425 }
2426 libs.unlockBuffer();
2427 }
2428 } else {
2429 err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
2430 if (err < 0) {
2431 goto bail;
2432 }
2433 err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
2434 if (err < 0) {
2435 goto bail;
2436 }
2437 }
2438
2439 // Write out the ProGuard file
2440 err = writeProguardFile(bundle, assets);
2441 if (err < 0) {
2442 goto bail;
2443 }
2444
2445 // Write the apk
2446 if (outputAPKFile) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002447 // Gather all resources and add them to the APK Builder. The builder will then
2448 // figure out which Split they belong in.
2449 err = addResourcesToBuilder(assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002450 if (err != NO_ERROR) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002451 goto bail;
2452 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002453
2454 const Vector<sp<ApkSplit> >& splits = builder->getSplits();
2455 const size_t numSplits = splits.size();
2456 for (size_t i = 0; i < numSplits; i++) {
2457 const sp<ApkSplit>& split = splits[i];
2458 String8 outputPath = buildApkName(String8(outputAPKFile), split);
2459 err = writeAPK(bundle, outputPath, split);
2460 if (err != NO_ERROR) {
2461 fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
2462 goto bail;
2463 }
2464 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002465 }
2466
2467 // If we've been asked to generate a dependency file, we need to finish up here.
2468 // the writeResourceSymbols and writeAPK functions have already written the target
2469 // half of the dependency file, now we need to write the prerequisites. (files that
2470 // the R.java file or .ap_ file depend on)
2471 if (bundle->getGenDependencies()) {
2472 // Now that writeResourceSymbols or writeAPK has taken care of writing
2473 // the targets to our dependency file, we'll write the prereqs
2474 fp = fopen(dependencyFile, "a+");
2475 fprintf(fp, " : ");
2476 bool includeRaw = (outputAPKFile != NULL);
2477 err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
2478 // Also manually add the AndroidManifeset since it's not under res/ or assets/
2479 // and therefore was not added to our pathstores during slurping
2480 fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
2481 fclose(fp);
2482 }
2483
2484 retVal = 0;
2485bail:
2486 if (SourcePos::hasErrors()) {
2487 SourcePos::printErrors(stderr);
2488 }
2489 return retVal;
2490}
2491
2492/*
2493 * Do PNG Crunching
2494 * PRECONDITIONS
2495 * -S flag points to a source directory containing drawable* folders
2496 * -C flag points to destination directory. The folder structure in the
2497 * source directory will be mirrored to the destination (cache) directory
2498 *
2499 * POSTCONDITIONS
2500 * Destination directory will be updated to match the PNG files in
Maurice Chu2675f762013-10-22 17:33:11 -07002501 * the source directory.
Adam Lesinski282e1812014-01-23 18:17:42 -08002502 */
2503int doCrunch(Bundle* bundle)
2504{
2505 fprintf(stdout, "Crunching PNG Files in ");
2506 fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
2507 fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
2508
2509 updatePreProcessedCache(bundle);
2510
2511 return NO_ERROR;
2512}
2513
2514/*
2515 * Do PNG Crunching on a single flag
2516 * -i points to a single png file
2517 * -o points to a single png output file
2518 */
2519int doSingleCrunch(Bundle* bundle)
2520{
2521 fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile());
2522 fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile());
2523
2524 String8 input(bundle->getSingleCrunchInputFile());
2525 String8 output(bundle->getSingleCrunchOutputFile());
2526
2527 if (preProcessImageToCache(bundle, input, output) != NO_ERROR) {
2528 // we can't return the status_t as it gets truncate to the lower 8 bits.
2529 return 42;
2530 }
2531
2532 return NO_ERROR;
2533}
2534
2535char CONSOLE_DATA[2925] = {
2536 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2537 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2538 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2539 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2540 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63,
2541 86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83,
2542 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2543 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2544 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81,
2545 81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32,
2546 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2547 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2548 32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59,
2549 59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2550 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2551 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59,
2552 59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32,
2553 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2554 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2555 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87,
2556 58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32,
2557 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2558 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2559 47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58,
2560 121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2561 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2562 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81,
2563 81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32,
2564 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2565 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2566 32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59,
2567 59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2568 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2569 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59,
2570 59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32,
2571 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2572 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2573 32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81,
2574 70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32,
2575 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2576 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2577 32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81,
2578 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2579 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2580 32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109,
2581 81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32,
2582 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2583 32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59,
2584 59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59,
2585 60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2586 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59,
2587 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46,
2588 61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32,
2589 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2590 32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2591 59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81,
2592 109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32,
2593 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81,
2594 67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2595 59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58,
2596 61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32,
2597 32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59,
2598 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37,
2599 73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2600 59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2601 32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59,
2602 59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96,
2603 46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61,
2604 97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32,
2605 46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61,
2606 119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59,
2607 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32,
2608 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2609 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119,
2610 119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2611 59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10,
2612 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2613 32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81,
2614 81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41,
2615 87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2616 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81,
2617 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58,
2618 45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32,
2619 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2620 32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2621 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32,
2622 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2623 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58,
2624 59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58,
2625 59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2626 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2627 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81,
2628 81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32,
2629 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2630 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2631 32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2632 81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2633 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2634 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106,
2635 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59,
2636 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2637 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2638 32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2639 81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32,
2640 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2641 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2642 58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59,
2643 61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2644 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2645 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81,
2646 81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32,
2647 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2648 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2649 32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2650 81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2651 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2652 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59,
2653 59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59,
2654 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2655 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2656 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33,
2657 58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32,
2658 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2659 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2660 61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59,
2661 59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32,
2662 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32,
2663 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95,
2664 32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59,
2665 61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2666 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2667 32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45,
2668 59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32,
2669 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32,
2670 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59,
2671 59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59,
2672 59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2673 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2674 32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2675 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2676 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2677 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2678 46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61,
2679 46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2680 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2681 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59,
2682 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59,
2683 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2684 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2685 32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32,
2686 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32,
2687 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2688 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61,
2689 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59,
2690 59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2691 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2692 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32,
2693 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32,
2694 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2695 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2696 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2697 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2698 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10
2699 };