blob: 41102fe7491ddf4561fe94583c61ce245f861bf6 [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 Lesinski63e646e2014-07-30 11:40:39 -0700767 const DynamicRefTable* dynamicRefTable = res.getDynamicRefTableForCookie(assetsCookie);
768 if (dynamicRefTable == NULL) {
769 fprintf(stderr, "ERROR: failed to find dynamic reference table for asset cookie %d\n",
770 assetsCookie);
771 return 1;
772 }
773
774 Asset* asset = NULL;
775
Adam Lesinski282e1812014-01-23 18:17:42 -0800776 if (strcmp("resources", option) == 0) {
777#ifndef HAVE_ANDROID_OS
778 res.print(bundle->getValues());
779#endif
780
781 } else if (strcmp("strings", option) == 0) {
782 const ResStringPool* pool = res.getTableStringBlock(0);
783 printStringPool(pool);
784
785 } else if (strcmp("xmltree", option) == 0) {
786 if (bundle->getFileSpecCount() < 3) {
787 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
788 goto bail;
789 }
790
791 for (int i=2; i<bundle->getFileSpecCount(); i++) {
792 const char* resname = bundle->getFileSpecEntry(i);
Adam Lesinski63e646e2014-07-30 11:40:39 -0700793 ResXMLTree tree(dynamicRefTable);
794 asset = assets.openNonAsset(assetsCookie, resname, Asset::ACCESS_BUFFER);
Adam Lesinski282e1812014-01-23 18:17:42 -0800795 if (asset == NULL) {
796 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
797 goto bail;
798 }
799
800 if (tree.setTo(asset->getBuffer(true),
801 asset->getLength()) != NO_ERROR) {
802 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
803 goto bail;
804 }
805 tree.restart();
806 printXMLBlock(&tree);
807 tree.uninit();
808 delete asset;
809 asset = NULL;
810 }
811
812 } else if (strcmp("xmlstrings", option) == 0) {
813 if (bundle->getFileSpecCount() < 3) {
814 fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
815 goto bail;
816 }
817
818 for (int i=2; i<bundle->getFileSpecCount(); i++) {
819 const char* resname = bundle->getFileSpecEntry(i);
Adam Lesinski63e646e2014-07-30 11:40:39 -0700820 asset = assets.openNonAsset(assetsCookie, resname, Asset::ACCESS_BUFFER);
Adam Lesinski282e1812014-01-23 18:17:42 -0800821 if (asset == NULL) {
822 fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
823 goto bail;
824 }
825
Adam Lesinski63e646e2014-07-30 11:40:39 -0700826 ResXMLTree tree(dynamicRefTable);
Adam Lesinski282e1812014-01-23 18:17:42 -0800827 if (tree.setTo(asset->getBuffer(true),
828 asset->getLength()) != NO_ERROR) {
829 fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
830 goto bail;
831 }
832 printStringPool(&tree.getStrings());
833 delete asset;
834 asset = NULL;
835 }
836
837 } else {
Adam Lesinski63e646e2014-07-30 11:40:39 -0700838 asset = assets.openNonAsset(assetsCookie, "AndroidManifest.xml", Asset::ACCESS_BUFFER);
Adam Lesinski282e1812014-01-23 18:17:42 -0800839 if (asset == NULL) {
840 fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
841 goto bail;
842 }
843
Adam Lesinski63e646e2014-07-30 11:40:39 -0700844 ResXMLTree tree(dynamicRefTable);
Adam Lesinski282e1812014-01-23 18:17:42 -0800845 if (tree.setTo(asset->getBuffer(true),
846 asset->getLength()) != NO_ERROR) {
847 fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
848 goto bail;
849 }
850 tree.restart();
851
852 if (strcmp("permissions", option) == 0) {
853 size_t len;
854 ResXMLTree::event_code_t code;
855 int depth = 0;
856 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
857 if (code == ResXMLTree::END_TAG) {
858 depth--;
859 continue;
860 }
861 if (code != ResXMLTree::START_TAG) {
862 continue;
863 }
864 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -0700865 const char16_t* ctag16 = tree.getElementName(&len);
866 if (ctag16 == NULL) {
867 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
868 goto bail;
869 }
870 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -0800871 //printf("Depth %d tag %s\n", depth, tag.string());
872 if (depth == 1) {
873 if (tag != "manifest") {
874 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
875 goto bail;
876 }
877 String8 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -0700878 printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800879 } else if (depth == 2 && tag == "permission") {
880 String8 error;
881 String8 name = getAttribute(tree, NAME_ATTR, &error);
882 if (error != "") {
883 fprintf(stderr, "ERROR: %s\n", error.string());
884 goto bail;
885 }
Maurice Chu2675f762013-10-22 17:33:11 -0700886 printf("permission: %s\n",
887 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -0800888 } else if (depth == 2 && tag == "uses-permission") {
889 String8 error;
890 String8 name = getAttribute(tree, NAME_ATTR, &error);
891 if (error != "") {
892 fprintf(stderr, "ERROR: %s\n", error.string());
893 goto bail;
894 }
Adam Lesinski58f1f362013-11-12 12:59:08 -0800895 printUsesPermission(name,
896 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
897 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
Adam Lesinski282e1812014-01-23 18:17:42 -0800898 }
899 }
900 } else if (strcmp("badging", option) == 0) {
901 Vector<String8> locales;
902 res.getLocales(&locales);
903
904 Vector<ResTable_config> configs;
905 res.getConfigurations(&configs);
906 SortedVector<int> densities;
907 const size_t NC = configs.size();
908 for (size_t i=0; i<NC; i++) {
909 int dens = configs[i].density;
910 if (dens == 0) {
911 dens = 160;
912 }
913 densities.add(dens);
914 }
915
916 size_t len;
917 ResXMLTree::event_code_t code;
918 int depth = 0;
919 String8 error;
920 bool withinActivity = false;
921 bool isMainActivity = false;
922 bool isLauncherActivity = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -0800923 bool isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800924 bool isSearchable = false;
925 bool withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -0700926 bool withinSupportsInput = false;
Adam Lesinski2c72b682014-06-24 09:56:01 -0700927 bool withinFeatureGroup = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800928 bool withinReceiver = false;
929 bool withinService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700930 bool withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800931 bool withinIntentFilter = false;
932 bool hasMainActivity = false;
933 bool hasOtherActivities = false;
934 bool hasOtherReceivers = false;
935 bool hasOtherServices = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700936 bool hasIntentFilter = false;
937
Adam Lesinski282e1812014-01-23 18:17:42 -0800938 bool hasWallpaperService = false;
939 bool hasImeService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700940 bool hasAccessibilityService = false;
941 bool hasPrintService = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800942 bool hasWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700943 bool hasDeviceAdminReceiver = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700944 bool hasPaymentService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700945 bool hasDocumentsProvider = false;
946 bool hasCameraActivity = false;
947 bool hasCameraSecureActivity = false;
948 bool hasLauncher = false;
949 bool hasNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400950 bool hasDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700951
Adam Lesinski282e1812014-01-23 18:17:42 -0800952 bool actMainActivity = false;
953 bool actWidgetReceivers = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700954 bool actDeviceAdminEnabled = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800955 bool actImeService = false;
956 bool actWallpaperService = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700957 bool actAccessibilityService = false;
958 bool actPrintService = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700959 bool actHostApduService = false;
960 bool actOffHostApduService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700961 bool actDocumentsProvider = false;
962 bool actNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400963 bool actDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700964 bool actCamera = false;
965 bool actCameraSecure = false;
966 bool catLauncher = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700967 bool hasMetaHostPaymentCategory = false;
968 bool hasMetaOffHostPaymentCategory = false;
Adam Lesinskia5018c92013-09-30 16:23:15 -0700969
970 // These permissions are required by services implementing services
971 // the system binds to (IME, Accessibility, PrintServices, etc.)
972 bool hasBindDeviceAdminPermission = false;
973 bool hasBindInputMethodPermission = false;
974 bool hasBindAccessibilityServicePermission = false;
975 bool hasBindPrintServicePermission = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -0700976 bool hasBindNfcServicePermission = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -0700977 bool hasRequiredSafAttributes = false;
978 bool hasBindNotificationListenerServicePermission = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -0400979 bool hasBindDreamServicePermission = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800980
981 // These two implement the implicit permissions that are granted
982 // to pre-1.6 applications.
983 bool hasWriteExternalStoragePermission = false;
984 bool hasReadPhoneStatePermission = false;
985
986 // If an app requests write storage, they will also get read storage.
987 bool hasReadExternalStoragePermission = false;
988
989 // Implement transition to read and write call log.
990 bool hasReadContactsPermission = false;
991 bool hasWriteContactsPermission = false;
992 bool hasReadCallLogPermission = false;
993 bool hasWriteCallLogPermission = false;
994
995 // This next group of variables is used to implement a group of
996 // backward-compatibility heuristics necessitated by the addition of
997 // some new uses-feature constants in 2.1 and 2.2. In most cases, the
998 // heuristic is "if an app requests a permission but doesn't explicitly
999 // request the corresponding <uses-feature>, presume it's there anyway".
Adam Lesinski2c72b682014-06-24 09:56:01 -07001000
Adam Lesinski282e1812014-01-23 18:17:42 -08001001 // 2.2 also added some other features that apps can request, but that
1002 // have no corresponding permission, so we cannot implement any
1003 // back-compatibility heuristic for them. The below are thus unnecessary
1004 // (but are retained here for documentary purposes.)
1005 //bool specCompassFeature = false;
1006 //bool specAccelerometerFeature = false;
1007 //bool specProximityFeature = false;
1008 //bool specAmbientLightFeature = false;
1009 //bool specLiveWallpaperFeature = false;
1010
1011 int targetSdk = 0;
1012 int smallScreen = 1;
1013 int normalScreen = 1;
1014 int largeScreen = 1;
1015 int xlargeScreen = 1;
1016 int anyDensity = 1;
1017 int requiresSmallestWidthDp = 0;
1018 int compatibleWidthLimitDp = 0;
1019 int largestWidthLimitDp = 0;
1020 String8 pkg;
1021 String8 activityName;
1022 String8 activityLabel;
1023 String8 activityIcon;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001024 String8 activityBanner;
Adam Lesinski282e1812014-01-23 18:17:42 -08001025 String8 receiverName;
1026 String8 serviceName;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001027 Vector<String8> supportedInput;
Adam Lesinski2c72b682014-06-24 09:56:01 -07001028
1029 FeatureGroup commonFeatures;
1030 Vector<FeatureGroup> featureGroups;
1031 KeyedVector<String8, ImpliedFeature> impliedFeatures;
1032
Adam Lesinski282e1812014-01-23 18:17:42 -08001033 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1034 if (code == ResXMLTree::END_TAG) {
1035 depth--;
1036 if (depth < 2) {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001037 if (withinSupportsInput && !supportedInput.isEmpty()) {
1038 printf("supports-input: '");
1039 const size_t N = supportedInput.size();
1040 for (size_t i=0; i<N; i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07001041 printf("%s", ResTable::normalizeForOutput(
1042 supportedInput[i].string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001043 if (i != N - 1) {
1044 printf("' '");
1045 } else {
1046 printf("'\n");
1047 }
1048 }
1049 supportedInput.clear();
1050 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001051 withinApplication = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001052 withinSupportsInput = false;
Adam Lesinski2c72b682014-06-24 09:56:01 -07001053 withinFeatureGroup = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001054 } else if (depth < 3) {
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001055 if (withinActivity && isMainActivity) {
Maurice Chu2675f762013-10-22 17:33:11 -07001056 String8 aName(getComponentName(pkg, activityName));
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001057 if (isLauncherActivity) {
1058 printf("launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001059 if (aName.length() > 0) {
1060 printf(" name='%s' ",
1061 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001062 }
1063 printf(" label='%s' icon='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001064 ResTable::normalizeForOutput(activityLabel.string()).string(),
1065 ResTable::normalizeForOutput(activityIcon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001066 }
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001067 if (isLeanbackLauncherActivity) {
1068 printf("leanback-launchable-activity:");
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001069 if (aName.length() > 0) {
1070 printf(" name='%s' ",
1071 ResTable::normalizeForOutput(aName.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001072 }
1073 printf(" label='%s' icon='%s' banner='%s'\n",
Tim Kilbourn9eaaaf02014-03-07 23:04:03 -08001074 ResTable::normalizeForOutput(activityLabel.string()).string(),
1075 ResTable::normalizeForOutput(activityIcon.string()).string(),
1076 ResTable::normalizeForOutput(activityBanner.string()).string());
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001077 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001078 }
1079 if (!hasIntentFilter) {
1080 hasOtherActivities |= withinActivity;
1081 hasOtherReceivers |= withinReceiver;
1082 hasOtherServices |= withinService;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001083 } else {
1084 if (withinService) {
1085 hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
1086 hasBindNfcServicePermission);
1087 hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
1088 hasBindNfcServicePermission);
1089 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001090 }
1091 withinActivity = false;
1092 withinService = false;
1093 withinReceiver = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001094 withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001095 hasIntentFilter = false;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001096 isMainActivity = isLauncherActivity = isLeanbackLauncherActivity = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001097 } else if (depth < 4) {
1098 if (withinIntentFilter) {
1099 if (withinActivity) {
1100 hasMainActivity |= actMainActivity;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001101 hasLauncher |= catLauncher;
1102 hasCameraActivity |= actCamera;
1103 hasCameraSecureActivity |= actCameraSecure;
1104 hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure;
Adam Lesinski282e1812014-01-23 18:17:42 -08001105 } else if (withinReceiver) {
1106 hasWidgetReceivers |= actWidgetReceivers;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001107 hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
1108 hasBindDeviceAdminPermission);
1109 hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
Adam Lesinski282e1812014-01-23 18:17:42 -08001110 } else if (withinService) {
1111 hasImeService |= actImeService;
1112 hasWallpaperService |= actWallpaperService;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001113 hasAccessibilityService |= (actAccessibilityService &&
1114 hasBindAccessibilityServicePermission);
1115 hasPrintService |= (actPrintService && hasBindPrintServicePermission);
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001116 hasNotificationListenerService |= actNotificationListenerService &&
1117 hasBindNotificationListenerServicePermission;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001118 hasDreamService |= actDreamService && hasBindDreamServicePermission;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001119 hasOtherServices |= (!actImeService && !actWallpaperService &&
Adam Lesinski94fc9122013-09-30 17:16:09 -07001120 !actAccessibilityService && !actPrintService &&
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001121 !actHostApduService && !actOffHostApduService &&
1122 !actNotificationListenerService);
1123 } else if (withinProvider) {
1124 hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes;
Adam Lesinski282e1812014-01-23 18:17:42 -08001125 }
1126 }
1127 withinIntentFilter = false;
1128 }
1129 continue;
1130 }
1131 if (code != ResXMLTree::START_TAG) {
1132 continue;
1133 }
1134 depth++;
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001135
1136 const char16_t* ctag16 = tree.getElementName(&len);
1137 if (ctag16 == NULL) {
1138 fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
1139 goto bail;
1140 }
1141 String8 tag(ctag16);
Adam Lesinski282e1812014-01-23 18:17:42 -08001142 //printf("Depth %d, %s\n", depth, tag.string());
1143 if (depth == 1) {
1144 if (tag != "manifest") {
1145 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
1146 goto bail;
1147 }
1148 pkg = getAttribute(tree, NULL, "package", NULL);
Maurice Chu2675f762013-10-22 17:33:11 -07001149 printf("package: name='%s' ",
1150 ResTable::normalizeForOutput(pkg.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001151 int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
1152 if (error != "") {
1153 fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
1154 goto bail;
1155 }
1156 if (versionCode > 0) {
1157 printf("versionCode='%d' ", versionCode);
1158 } else {
1159 printf("versionCode='' ");
1160 }
1161 String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
1162 if (error != "") {
1163 fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
1164 goto bail;
1165 }
Adam Lesinski25d35a92014-08-11 09:41:56 -07001166 printf("versionName='%s'",
Maurice Chu2675f762013-10-22 17:33:11 -07001167 ResTable::normalizeForOutput(versionName.string()).string());
Adam Lesinski25d35a92014-08-11 09:41:56 -07001168
1169 String8 splitName = getAttribute(tree, NULL, "split", NULL);
1170 if (!splitName.isEmpty()) {
1171 printf(" split='%s'", ResTable::normalizeForOutput(
1172 splitName.string()).string());
1173 }
1174 printf("\n");
Adam Lesinski282e1812014-01-23 18:17:42 -08001175 } else if (depth == 2) {
1176 withinApplication = false;
1177 if (tag == "application") {
1178 withinApplication = true;
1179
1180 String8 label;
1181 const size_t NL = locales.size();
1182 for (size_t i=0; i<NL; i++) {
1183 const char* localeStr = locales[i].string();
1184 assets.setLocale(localeStr != NULL ? localeStr : "");
1185 String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1186 if (llabel != "") {
1187 if (localeStr == NULL || strlen(localeStr) == 0) {
1188 label = llabel;
Maurice Chu2675f762013-10-22 17:33:11 -07001189 printf("application-label:'%s'\n",
1190 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001191 } else {
1192 if (label == "") {
1193 label = llabel;
1194 }
1195 printf("application-label-%s:'%s'\n", localeStr,
Maurice Chu2675f762013-10-22 17:33:11 -07001196 ResTable::normalizeForOutput(llabel.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001197 }
1198 }
1199 }
1200
1201 ResTable_config tmpConfig = config;
1202 const size_t ND = densities.size();
1203 for (size_t i=0; i<ND; i++) {
1204 tmpConfig.density = densities[i];
1205 assets.setConfiguration(tmpConfig);
1206 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1207 if (icon != "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001208 printf("application-icon-%d:'%s'\n", densities[i],
1209 ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001210 }
1211 }
1212 assets.setConfiguration(config);
1213
1214 String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1215 if (error != "") {
1216 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
1217 goto bail;
1218 }
1219 int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
1220 if (error != "") {
1221 fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
1222 goto bail;
1223 }
Maurice Chu2675f762013-10-22 17:33:11 -07001224 printf("application: label='%s' ",
1225 ResTable::normalizeForOutput(label.string()).string());
1226 printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001227 if (testOnly != 0) {
1228 printf("testOnly='%d'\n", testOnly);
1229 }
1230
1231 int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
1232 if (error != "") {
1233 fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
1234 goto bail;
1235 }
1236 if (debuggable != 0) {
1237 printf("application-debuggable\n");
1238 }
1239 } else if (tag == "uses-sdk") {
1240 int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
1241 if (error != "") {
1242 error = "";
1243 String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
1244 if (error != "") {
1245 fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
1246 error.string());
1247 goto bail;
1248 }
1249 if (name == "Donut") targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001250 printf("sdkVersion:'%s'\n",
1251 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001252 } else if (code != -1) {
1253 targetSdk = code;
1254 printf("sdkVersion:'%d'\n", code);
1255 }
1256 code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
1257 if (code != -1) {
1258 printf("maxSdkVersion:'%d'\n", code);
1259 }
1260 code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
1261 if (error != "") {
1262 error = "";
1263 String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
1264 if (error != "") {
1265 fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
1266 error.string());
1267 goto bail;
1268 }
1269 if (name == "Donut" && targetSdk < 4) targetSdk = 4;
Maurice Chu2675f762013-10-22 17:33:11 -07001270 printf("targetSdkVersion:'%s'\n",
1271 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001272 } else if (code != -1) {
1273 if (targetSdk < code) {
1274 targetSdk = code;
1275 }
1276 printf("targetSdkVersion:'%d'\n", code);
1277 }
1278 } else if (tag == "uses-configuration") {
1279 int32_t reqTouchScreen = getIntegerAttribute(tree,
1280 REQ_TOUCH_SCREEN_ATTR, NULL, 0);
1281 int32_t reqKeyboardType = getIntegerAttribute(tree,
1282 REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
1283 int32_t reqHardKeyboard = getIntegerAttribute(tree,
1284 REQ_HARD_KEYBOARD_ATTR, NULL, 0);
1285 int32_t reqNavigation = getIntegerAttribute(tree,
1286 REQ_NAVIGATION_ATTR, NULL, 0);
1287 int32_t reqFiveWayNav = getIntegerAttribute(tree,
1288 REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
1289 printf("uses-configuration:");
1290 if (reqTouchScreen != 0) {
1291 printf(" reqTouchScreen='%d'", reqTouchScreen);
1292 }
1293 if (reqKeyboardType != 0) {
1294 printf(" reqKeyboardType='%d'", reqKeyboardType);
1295 }
1296 if (reqHardKeyboard != 0) {
1297 printf(" reqHardKeyboard='%d'", reqHardKeyboard);
1298 }
1299 if (reqNavigation != 0) {
1300 printf(" reqNavigation='%d'", reqNavigation);
1301 }
1302 if (reqFiveWayNav != 0) {
1303 printf(" reqFiveWayNav='%d'", reqFiveWayNav);
1304 }
1305 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001306 } else if (tag == "supports-input") {
1307 withinSupportsInput = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001308 } else if (tag == "supports-screens") {
1309 smallScreen = getIntegerAttribute(tree,
1310 SMALL_SCREEN_ATTR, NULL, 1);
1311 normalScreen = getIntegerAttribute(tree,
1312 NORMAL_SCREEN_ATTR, NULL, 1);
1313 largeScreen = getIntegerAttribute(tree,
1314 LARGE_SCREEN_ATTR, NULL, 1);
1315 xlargeScreen = getIntegerAttribute(tree,
1316 XLARGE_SCREEN_ATTR, NULL, 1);
1317 anyDensity = getIntegerAttribute(tree,
1318 ANY_DENSITY_ATTR, NULL, 1);
1319 requiresSmallestWidthDp = getIntegerAttribute(tree,
1320 REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
1321 compatibleWidthLimitDp = getIntegerAttribute(tree,
1322 COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
1323 largestWidthLimitDp = getIntegerAttribute(tree,
1324 LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
Adam Lesinski2c72b682014-06-24 09:56:01 -07001325 } else if (tag == "feature-group") {
1326 withinFeatureGroup = true;
1327 FeatureGroup group;
1328 group.label = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1329 if (error != "") {
1330 fprintf(stderr, "ERROR getting 'android:label' attribute:"
1331 " %s\n", error.string());
1332 goto bail;
1333 }
1334 featureGroups.add(group);
1335
Adam Lesinski282e1812014-01-23 18:17:42 -08001336 } else if (tag == "uses-feature") {
1337 String8 name = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001338 if (name != "" && error == "") {
1339 int req = getIntegerAttribute(tree,
1340 REQUIRED_ATTR, NULL, 1);
1341
Adam Lesinski2c72b682014-06-24 09:56:01 -07001342 commonFeatures.features.add(name, req);
1343 if (req) {
1344 addParentFeatures(&commonFeatures, name);
Adam Lesinski282e1812014-01-23 18:17:42 -08001345 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001346 } else {
1347 int vers = getIntegerAttribute(tree,
1348 GL_ES_VERSION_ATTR, &error);
1349 if (error == "") {
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001350 if (vers > commonFeatures.openGLESVersion) {
1351 commonFeatures.openGLESVersion = vers;
1352 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001353 }
1354 }
1355 } else if (tag == "uses-permission") {
1356 String8 name = getAttribute(tree, NAME_ATTR, &error);
1357 if (name != "" && error == "") {
1358 if (name == "android.permission.CAMERA") {
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001359 addImpliedFeature(&impliedFeatures, "android.hardware.camera",
Adam Lesinski2c72b682014-06-24 09:56:01 -07001360 String8::format("requested %s permission", name.string())
1361 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001362 } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001363 addImpliedFeature(&impliedFeatures, "android.hardware.location.gps",
1364 String8::format("requested %s permission", name.string())
1365 .string());
1366 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1367 String8::format("requested %s permission", name.string())
1368 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001369 } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001370 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1371 String8::format("requested %s permission", name.string())
1372 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001373 } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001374 addImpliedFeature(&impliedFeatures, "android.hardware.location.network",
1375 String8::format("requested %s permission", name.string())
1376 .string());
1377 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1378 String8::format("requested %s permission", name.string())
1379 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001380 } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
1381 name == "android.permission.INSTALL_LOCATION_PROVIDER") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001382 addImpliedFeature(&impliedFeatures, "android.hardware.location",
1383 String8::format("requested %s permission", name.string())
1384 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001385 } else if (name == "android.permission.BLUETOOTH" ||
1386 name == "android.permission.BLUETOOTH_ADMIN") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001387 if (targetSdk > 4) {
1388 addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
1389 String8::format("requested %s permission", name.string())
1390 .string());
1391 addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
1392 "targetSdkVersion > 4");
1393 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001394 } else if (name == "android.permission.RECORD_AUDIO") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001395 addImpliedFeature(&impliedFeatures, "android.hardware.microphone",
1396 String8::format("requested %s permission", name.string())
1397 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001398 } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
1399 name == "android.permission.CHANGE_WIFI_STATE" ||
1400 name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001401 addImpliedFeature(&impliedFeatures, "android.hardware.wifi",
1402 String8::format("requested %s permission", name.string())
1403 .string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001404 } else if (name == "android.permission.CALL_PHONE" ||
1405 name == "android.permission.CALL_PRIVILEGED" ||
1406 name == "android.permission.MODIFY_PHONE_STATE" ||
1407 name == "android.permission.PROCESS_OUTGOING_CALLS" ||
1408 name == "android.permission.READ_SMS" ||
1409 name == "android.permission.RECEIVE_SMS" ||
1410 name == "android.permission.RECEIVE_MMS" ||
1411 name == "android.permission.RECEIVE_WAP_PUSH" ||
1412 name == "android.permission.SEND_SMS" ||
1413 name == "android.permission.WRITE_APN_SETTINGS" ||
1414 name == "android.permission.WRITE_SMS") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001415 addImpliedFeature(&impliedFeatures, "android.hardware.telephony",
1416 String8("requested a telephony permission").string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001417 } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
1418 hasWriteExternalStoragePermission = true;
1419 } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
1420 hasReadExternalStoragePermission = true;
1421 } else if (name == "android.permission.READ_PHONE_STATE") {
1422 hasReadPhoneStatePermission = true;
1423 } else if (name == "android.permission.READ_CONTACTS") {
1424 hasReadContactsPermission = true;
1425 } else if (name == "android.permission.WRITE_CONTACTS") {
1426 hasWriteContactsPermission = true;
1427 } else if (name == "android.permission.READ_CALL_LOG") {
1428 hasReadCallLogPermission = true;
1429 } else if (name == "android.permission.WRITE_CALL_LOG") {
1430 hasWriteCallLogPermission = true;
1431 }
Adam Lesinski58f1f362013-11-12 12:59:08 -08001432
1433 printUsesPermission(name,
1434 getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
1435 getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
1436 } else {
Adam Lesinski282e1812014-01-23 18:17:42 -08001437 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1438 error.string());
1439 goto bail;
1440 }
1441 } else if (tag == "uses-package") {
1442 String8 name = getAttribute(tree, NAME_ATTR, &error);
1443 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001444 printf("uses-package:'%s'\n",
1445 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001446 } else {
1447 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1448 error.string());
1449 goto bail;
1450 }
1451 } else if (tag == "original-package") {
1452 String8 name = getAttribute(tree, NAME_ATTR, &error);
1453 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001454 printf("original-package:'%s'\n",
1455 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001456 } else {
1457 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1458 error.string());
1459 goto bail;
1460 }
1461 } else if (tag == "supports-gl-texture") {
1462 String8 name = getAttribute(tree, NAME_ATTR, &error);
1463 if (name != "" && error == "") {
Maurice Chu2675f762013-10-22 17:33:11 -07001464 printf("supports-gl-texture:'%s'\n",
1465 ResTable::normalizeForOutput(name.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001466 } else {
1467 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1468 error.string());
1469 goto bail;
1470 }
1471 } else if (tag == "compatible-screens") {
Adam Lesinski9cb2c682014-05-15 12:37:54 -07001472 printCompatibleScreens(tree, &error);
1473 if (error != "") {
1474 fprintf(stderr, "ERROR getting compatible screens: %s\n",
1475 error.string());
1476 goto bail;
1477 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001478 depth--;
1479 } else if (tag == "package-verifier") {
1480 String8 name = getAttribute(tree, NAME_ATTR, &error);
1481 if (name != "" && error == "") {
1482 String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
1483 if (publicKey != "" && error == "") {
1484 printf("package-verifier: name='%s' publicKey='%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001485 ResTable::normalizeForOutput(name.string()).string(),
1486 ResTable::normalizeForOutput(publicKey.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001487 }
1488 }
1489 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001490 } else if (depth == 3) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001491 withinActivity = false;
1492 withinReceiver = false;
1493 withinService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001494 withinProvider = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001495 hasIntentFilter = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001496 hasMetaHostPaymentCategory = false;
1497 hasMetaOffHostPaymentCategory = false;
1498 hasBindDeviceAdminPermission = false;
1499 hasBindInputMethodPermission = false;
1500 hasBindAccessibilityServicePermission = false;
1501 hasBindPrintServicePermission = false;
1502 hasBindNfcServicePermission = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001503 hasRequiredSafAttributes = false;
1504 hasBindNotificationListenerServicePermission = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001505 hasBindDreamServicePermission = false;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001506 if (withinApplication) {
1507 if(tag == "activity") {
1508 withinActivity = true;
1509 activityName = getAttribute(tree, NAME_ATTR, &error);
Adam Lesinski282e1812014-01-23 18:17:42 -08001510 if (error != "") {
Michael Wrightec4fdec2013-09-06 16:50:52 -07001511 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1512 error.string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001513 goto bail;
1514 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001515
Michael Wrightec4fdec2013-09-06 16:50:52 -07001516 activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
1517 if (error != "") {
1518 fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
1519 error.string());
1520 goto bail;
1521 }
1522
1523 activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
1524 if (error != "") {
1525 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
1526 error.string());
1527 goto bail;
1528 }
1529
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001530 activityBanner = getResolvedAttribute(&res, tree, BANNER_ATTR, &error);
1531 if (error != "") {
1532 fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
1533 error.string());
1534 goto bail;
1535 }
1536
Michael Wrightec4fdec2013-09-06 16:50:52 -07001537 int32_t orien = getResolvedIntegerAttribute(&res, tree,
1538 SCREEN_ORIENTATION_ATTR, &error);
1539 if (error == "") {
1540 if (orien == 0 || orien == 6 || orien == 8) {
1541 // Requests landscape, sensorLandscape, or reverseLandscape.
Adam Lesinski2c72b682014-06-24 09:56:01 -07001542 addImpliedFeature(&impliedFeatures, "android.hardware.screen.landscape",
1543 "one or more activities have specified a landscape orientation");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001544 } else if (orien == 1 || orien == 7 || orien == 9) {
1545 // Requests portrait, sensorPortrait, or reversePortrait.
Adam Lesinski2c72b682014-06-24 09:56:01 -07001546 addImpliedFeature(&impliedFeatures, "android.hardware.screen.portrait",
1547 "one or more activities have specified a portrait orientation");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001548 }
1549 }
1550 } else if (tag == "uses-library") {
1551 String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
1552 if (error != "") {
1553 fprintf(stderr,
1554 "ERROR getting 'android:name' attribute for uses-library"
1555 " %s\n", error.string());
1556 goto bail;
1557 }
1558 int req = getIntegerAttribute(tree,
1559 REQUIRED_ATTR, NULL, 1);
1560 printf("uses-library%s:'%s'\n",
Maurice Chu2675f762013-10-22 17:33:11 -07001561 req ? "" : "-not-required", ResTable::normalizeForOutput(
1562 libraryName.string()).string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001563 } else if (tag == "receiver") {
1564 withinReceiver = true;
1565 receiverName = getAttribute(tree, NAME_ATTR, &error);
1566
1567 if (error != "") {
1568 fprintf(stderr,
1569 "ERROR getting 'android:name' attribute for receiver:"
1570 " %s\n", error.string());
1571 goto bail;
1572 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001573
1574 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1575 if (error == "") {
1576 if (permission == "android.permission.BIND_DEVICE_ADMIN") {
1577 hasBindDeviceAdminPermission = true;
1578 }
1579 } else {
1580 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1581 " receiver '%s': %s\n", receiverName.string(), error.string());
1582 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001583 } else if (tag == "service") {
1584 withinService = true;
1585 serviceName = getAttribute(tree, NAME_ATTR, &error);
1586
1587 if (error != "") {
1588 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1589 "service:%s\n", error.string());
1590 goto bail;
1591 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001592
1593 String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
1594 if (error == "") {
1595 if (permission == "android.permission.BIND_INPUT_METHOD") {
1596 hasBindInputMethodPermission = true;
1597 } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
1598 hasBindAccessibilityServicePermission = true;
1599 } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
1600 hasBindPrintServicePermission = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001601 } else if (permission == "android.permission.BIND_NFC_SERVICE") {
1602 hasBindNfcServicePermission = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001603 } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
1604 hasBindNotificationListenerServicePermission = true;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001605 } else if (permission == "android.permission.BIND_DREAM_SERVICE") {
1606 hasBindDreamServicePermission = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001607 }
1608 } else {
1609 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
1610 " service '%s': %s\n", serviceName.string(), error.string());
1611 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001612 } else if (tag == "provider") {
1613 withinProvider = true;
1614
1615 bool exported = getResolvedIntegerAttribute(&res, tree, EXPORTED_ATTR, &error);
1616 if (error != "") {
1617 fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:"
1618 " %s\n", error.string());
1619 goto bail;
1620 }
1621
1622 bool grantUriPermissions = getResolvedIntegerAttribute(&res, tree,
1623 GRANT_URI_PERMISSIONS_ATTR, &error);
1624 if (error != "") {
1625 fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:"
1626 " %s\n", error.string());
1627 goto bail;
1628 }
1629
1630 String8 permission = getResolvedAttribute(&res, tree, PERMISSION_ATTR, &error);
1631 if (error != "") {
1632 fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:"
1633 " %s\n", error.string());
1634 goto bail;
1635 }
1636
1637 hasRequiredSafAttributes |= exported && grantUriPermissions &&
1638 permission == "android.permission.MANAGE_DOCUMENTS";
1639
Michael Wrightec4fdec2013-09-06 16:50:52 -07001640 } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
Adam Lesinskib71adb62014-05-15 14:14:41 -07001641 String8 metaDataName = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001642 if (error != "") {
1643 fprintf(stderr, "ERROR getting 'android:name' attribute for "
1644 "meta-data:%s\n", error.string());
1645 goto bail;
1646 }
Maurice Chu2675f762013-10-22 17:33:11 -07001647 printf("meta-data: name='%s' ",
1648 ResTable::normalizeForOutput(metaDataName.string()).string());
Maurice Chu76327312013-10-16 18:28:46 -07001649 printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"),
1650 &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001651 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001652 // Try looking for a RESOURCE_ATTR
1653 error = "";
1654 printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR,
1655 String8("resource"), &error);
Michael Wrightec4fdec2013-09-06 16:50:52 -07001656 if (error != "") {
Maurice Chu76327312013-10-16 18:28:46 -07001657 fprintf(stderr, "ERROR getting 'android:value' or "
1658 "'android:resource' attribute for "
1659 "meta-data:%s\n", error.string());
Michael Wrightec4fdec2013-09-06 16:50:52 -07001660 goto bail;
1661 }
Michael Wrightec4fdec2013-09-06 16:50:52 -07001662 }
Maurice Chu76327312013-10-16 18:28:46 -07001663 printf("\n");
Michael Wrightec4fdec2013-09-06 16:50:52 -07001664 } else if (withinSupportsInput && tag == "input-type") {
1665 String8 name = getAttribute(tree, NAME_ATTR, &error);
1666 if (name != "" && error == "") {
1667 supportedInput.add(name);
1668 } else {
1669 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1670 error.string());
1671 goto bail;
1672 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001673 }
Adam Lesinski2c72b682014-06-24 09:56:01 -07001674 } else if (withinFeatureGroup && tag == "uses-feature") {
Adam Lesinski2c72b682014-06-24 09:56:01 -07001675 FeatureGroup& top = featureGroups.editTop();
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001676
1677 String8 name = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
1678 if (name != "" && error == "") {
1679 int required = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
1680 top.features.add(name, required);
1681 if (required) {
1682 addParentFeatures(&top, name);
1683 }
1684
1685 } else {
1686 int vers = getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error);
1687 if (error == "") {
1688 if (vers > top.openGLESVersion) {
1689 top.openGLESVersion = vers;
1690 }
1691 }
Adam Lesinski2c72b682014-06-24 09:56:01 -07001692 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001693 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001694 } else if (depth == 4) {
1695 if (tag == "intent-filter") {
1696 hasIntentFilter = true;
1697 withinIntentFilter = true;
1698 actMainActivity = false;
1699 actWidgetReceivers = false;
1700 actImeService = false;
1701 actWallpaperService = false;
1702 actAccessibilityService = false;
1703 actPrintService = false;
1704 actDeviceAdminEnabled = false;
1705 actHostApduService = false;
1706 actOffHostApduService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001707 actDocumentsProvider = false;
1708 actNotificationListenerService = false;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001709 actDreamService = false;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001710 actCamera = false;
1711 actCameraSecure = false;
1712 catLauncher = false;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001713 } else if (withinService && tag == "meta-data") {
1714 String8 name = getAttribute(tree, NAME_ATTR, &error);
1715 if (error != "") {
1716 fprintf(stderr, "ERROR getting 'android:name' attribute for"
1717 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1718 goto bail;
1719 }
1720
1721 if (name == "android.nfc.cardemulation.host_apdu_service" ||
1722 name == "android.nfc.cardemulation.off_host_apdu_service") {
1723 bool offHost = true;
1724 if (name == "android.nfc.cardemulation.host_apdu_service") {
1725 offHost = false;
1726 }
1727
1728 String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
1729 if (error != "") {
1730 fprintf(stderr, "ERROR getting 'android:resource' attribute for"
1731 " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
1732 goto bail;
1733 }
1734
1735 Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
1736 offHost, &error);
1737 if (error != "") {
1738 fprintf(stderr, "ERROR getting AID category for service '%s'\n",
1739 serviceName.string());
1740 goto bail;
1741 }
1742
1743 const size_t catLen = categories.size();
1744 for (size_t i = 0; i < catLen; i++) {
1745 bool paymentCategory = (categories[i] == "payment");
1746 if (offHost) {
1747 hasMetaOffHostPaymentCategory |= paymentCategory;
1748 } else {
1749 hasMetaHostPaymentCategory |= paymentCategory;
1750 }
1751 }
1752 }
1753 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001754 } else if ((depth == 5) && withinIntentFilter) {
1755 String8 action;
1756 if (tag == "action") {
1757 action = getAttribute(tree, NAME_ATTR, &error);
1758 if (error != "") {
1759 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
1760 error.string());
1761 goto bail;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001762 }
1763
Adam Lesinskia5018c92013-09-30 16:23:15 -07001764 if (withinActivity) {
1765 if (action == "android.intent.action.MAIN") {
1766 isMainActivity = true;
1767 actMainActivity = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001768 } else if (action == "android.media.action.STILL_IMAGE_CAMERA" ||
1769 action == "android.media.action.VIDEO_CAMERA") {
1770 actCamera = true;
1771 } else if (action == "android.media.action.STILL_IMAGE_CAMERA_SECURE") {
1772 actCameraSecure = true;
Michael Wrightec4fdec2013-09-06 16:50:52 -07001773 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001774 } else if (withinReceiver) {
1775 if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
1776 actWidgetReceivers = true;
1777 } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
1778 actDeviceAdminEnabled = true;
1779 }
1780 } else if (withinService) {
1781 if (action == "android.view.InputMethod") {
1782 actImeService = true;
1783 } else if (action == "android.service.wallpaper.WallpaperService") {
1784 actWallpaperService = true;
1785 } else if (action == "android.accessibilityservice.AccessibilityService") {
1786 actAccessibilityService = true;
1787 } else if (action == "android.printservice.PrintService") {
1788 actPrintService = true;
Adam Lesinski94fc9122013-09-30 17:16:09 -07001789 } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
1790 actHostApduService = true;
1791 } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
1792 actOffHostApduService = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001793 } else if (action == "android.service.notification.NotificationListenerService") {
1794 actNotificationListenerService = true;
John Spurlockeb8d1be2014-06-25 17:46:15 -04001795 } else if (action == "android.service.dreams.DreamService") {
1796 actDreamService = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001797 }
1798 } else if (withinProvider) {
1799 if (action == "android.content.action.DOCUMENTS_PROVIDER") {
1800 actDocumentsProvider = true;
Adam Lesinskia5018c92013-09-30 16:23:15 -07001801 }
1802 }
1803 if (action == "android.intent.action.SEARCH") {
1804 isSearchable = true;
1805 }
1806 }
1807
1808 if (tag == "category") {
1809 String8 category = getAttribute(tree, NAME_ATTR, &error);
1810 if (error != "") {
1811 fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
1812 error.string());
1813 goto bail;
1814 }
1815 if (withinActivity) {
1816 if (category == "android.intent.category.LAUNCHER") {
1817 isLauncherActivity = true;
Tim Kilbourn0a5a5d62014-03-07 15:12:50 -08001818 } else if (category == "android.intent.category.LEANBACK_LAUNCHER") {
1819 isLeanbackLauncherActivity = true;
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001820 } else if (category == "android.intent.category.HOME") {
1821 catLauncher = true;
Adam Lesinski282e1812014-01-23 18:17:42 -08001822 }
1823 }
1824 }
1825 }
1826 }
1827
1828 // Pre-1.6 implicitly granted permission compatibility logic
1829 if (targetSdk < 4) {
1830 if (!hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001831 printUsesPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"));
1832 printUsesImpliedPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"),
1833 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001834 hasWriteExternalStoragePermission = true;
1835 }
1836 if (!hasReadPhoneStatePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001837 printUsesPermission(String8("android.permission.READ_PHONE_STATE"));
1838 printUsesImpliedPermission(String8("android.permission.READ_PHONE_STATE"),
1839 String8("targetSdkVersion < 4"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001840 }
1841 }
1842
1843 // If the application has requested WRITE_EXTERNAL_STORAGE, we will
1844 // force them to always take READ_EXTERNAL_STORAGE as well. We always
1845 // do this (regardless of target API version) because we can't have
1846 // an app with write permission but not read permission.
1847 if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001848 printUsesPermission(String8("android.permission.READ_EXTERNAL_STORAGE"));
1849 printUsesImpliedPermission(String8("android.permission.READ_EXTERNAL_STORAGE"),
1850 String8("requested WRITE_EXTERNAL_STORAGE"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001851 }
1852
1853 // Pre-JellyBean call log permission compatibility.
1854 if (targetSdk < 16) {
1855 if (!hasReadCallLogPermission && hasReadContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001856 printUsesPermission(String8("android.permission.READ_CALL_LOG"));
1857 printUsesImpliedPermission(String8("android.permission.READ_CALL_LOG"),
1858 String8("targetSdkVersion < 16 and requested READ_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001859 }
1860 if (!hasWriteCallLogPermission && hasWriteContactsPermission) {
Adam Lesinski58f1f362013-11-12 12:59:08 -08001861 printUsesPermission(String8("android.permission.WRITE_CALL_LOG"));
1862 printUsesImpliedPermission(String8("android.permission.WRITE_CALL_LOG"),
1863 String8("targetSdkVersion < 16 and requested WRITE_CONTACTS"));
Adam Lesinski282e1812014-01-23 18:17:42 -08001864 }
1865 }
1866
Adam Lesinski2c72b682014-06-24 09:56:01 -07001867 addImpliedFeature(&impliedFeatures, "android.hardware.touchscreen",
1868 "default feature for all apps");
1869
1870 const size_t numFeatureGroups = featureGroups.size();
1871 if (numFeatureGroups == 0) {
1872 // If no <feature-group> tags were defined, apply auto-implied features.
1873 printFeatureGroup(commonFeatures, &impliedFeatures);
1874
1875 } else {
1876 // <feature-group> tags are defined, so we ignore implied features and
1877 for (size_t i = 0; i < numFeatureGroups; i++) {
1878 FeatureGroup& grp = featureGroups.editItemAt(i);
1879
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001880 if (commonFeatures.openGLESVersion > grp.openGLESVersion) {
1881 grp.openGLESVersion = commonFeatures.openGLESVersion;
1882 }
1883
Adam Lesinski2c72b682014-06-24 09:56:01 -07001884 // Merge the features defined in the top level (not inside a <feature-group>)
1885 // with this feature group.
1886 const size_t numCommonFeatures = commonFeatures.features.size();
1887 for (size_t j = 0; j < numCommonFeatures; j++) {
1888 if (grp.features.indexOfKey(commonFeatures.features.keyAt(j)) < 0) {
Adam Lesinskid7a94da2014-07-25 14:38:54 -07001889 grp.features.add(commonFeatures.features.keyAt(j),
1890 commonFeatures.features[j]);
Adam Lesinski2c72b682014-06-24 09:56:01 -07001891 }
1892 }
1893
1894 if (!grp.features.isEmpty()) {
1895 printFeatureGroup(grp);
Adam Lesinski282e1812014-01-23 18:17:42 -08001896 }
1897 }
1898 }
1899
Adam Lesinski282e1812014-01-23 18:17:42 -08001900
Adam Lesinski282e1812014-01-23 18:17:42 -08001901 if (hasWidgetReceivers) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001902 printComponentPresence("app-widget");
Adam Lesinski282e1812014-01-23 18:17:42 -08001903 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001904 if (hasDeviceAdminReceiver) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001905 printComponentPresence("device-admin");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001906 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001907 if (hasImeService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001908 printComponentPresence("ime");
Adam Lesinski282e1812014-01-23 18:17:42 -08001909 }
1910 if (hasWallpaperService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001911 printComponentPresence("wallpaper");
Adam Lesinski282e1812014-01-23 18:17:42 -08001912 }
Adam Lesinskia5018c92013-09-30 16:23:15 -07001913 if (hasAccessibilityService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001914 printComponentPresence("accessibility");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001915 }
1916 if (hasPrintService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001917 printComponentPresence("print-service");
Adam Lesinskia5018c92013-09-30 16:23:15 -07001918 }
Adam Lesinski94fc9122013-09-30 17:16:09 -07001919 if (hasPaymentService) {
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001920 printComponentPresence("payment");
1921 }
1922 if (isSearchable) {
1923 printComponentPresence("search");
1924 }
1925 if (hasDocumentsProvider) {
1926 printComponentPresence("document-provider");
1927 }
1928 if (hasLauncher) {
1929 printComponentPresence("launcher");
1930 }
1931 if (hasNotificationListenerService) {
1932 printComponentPresence("notification-listener");
1933 }
John Spurlockeb8d1be2014-06-25 17:46:15 -04001934 if (hasDreamService) {
1935 printComponentPresence("dream");
1936 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001937 if (hasCameraActivity) {
1938 printComponentPresence("camera");
1939 }
1940 if (hasCameraSecureActivity) {
1941 printComponentPresence("camera-secure");
1942 }
1943
1944 if (hasMainActivity) {
1945 printf("main\n");
Adam Lesinski94fc9122013-09-30 17:16:09 -07001946 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001947 if (hasOtherActivities) {
1948 printf("other-activities\n");
1949 }
Adam Lesinski9d5b08e2014-04-25 11:01:43 -07001950 if (hasOtherReceivers) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001951 printf("other-receivers\n");
1952 }
1953 if (hasOtherServices) {
1954 printf("other-services\n");
1955 }
1956
1957 // For modern apps, if screen size buckets haven't been specified
1958 // but the new width ranges have, then infer the buckets from them.
1959 if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0
1960 && requiresSmallestWidthDp > 0) {
1961 int compatWidth = compatibleWidthLimitDp;
1962 if (compatWidth <= 0) {
1963 compatWidth = requiresSmallestWidthDp;
1964 }
1965 if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) {
1966 smallScreen = -1;
1967 } else {
1968 smallScreen = 0;
1969 }
1970 if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) {
1971 normalScreen = -1;
1972 } else {
1973 normalScreen = 0;
1974 }
1975 if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) {
1976 largeScreen = -1;
1977 } else {
1978 largeScreen = 0;
1979 }
1980 if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) {
1981 xlargeScreen = -1;
1982 } else {
1983 xlargeScreen = 0;
1984 }
1985 }
1986
1987 // Determine default values for any unspecified screen sizes,
1988 // based on the target SDK of the package. As of 4 (donut)
1989 // the screen size support was introduced, so all default to
1990 // enabled.
1991 if (smallScreen > 0) {
1992 smallScreen = targetSdk >= 4 ? -1 : 0;
1993 }
1994 if (normalScreen > 0) {
1995 normalScreen = -1;
1996 }
1997 if (largeScreen > 0) {
1998 largeScreen = targetSdk >= 4 ? -1 : 0;
1999 }
2000 if (xlargeScreen > 0) {
2001 // Introduced in Gingerbread.
2002 xlargeScreen = targetSdk >= 9 ? -1 : 0;
2003 }
2004 if (anyDensity > 0) {
2005 anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0
2006 || compatibleWidthLimitDp > 0) ? -1 : 0;
2007 }
2008 printf("supports-screens:");
2009 if (smallScreen != 0) {
2010 printf(" 'small'");
2011 }
2012 if (normalScreen != 0) {
2013 printf(" 'normal'");
2014 }
2015 if (largeScreen != 0) {
2016 printf(" 'large'");
2017 }
2018 if (xlargeScreen != 0) {
2019 printf(" 'xlarge'");
2020 }
2021 printf("\n");
2022 printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
2023 if (requiresSmallestWidthDp > 0) {
2024 printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp);
2025 }
2026 if (compatibleWidthLimitDp > 0) {
2027 printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp);
2028 }
2029 if (largestWidthLimitDp > 0) {
2030 printf("largest-width-limit:'%d'\n", largestWidthLimitDp);
2031 }
2032
2033 printf("locales:");
2034 const size_t NL = locales.size();
2035 for (size_t i=0; i<NL; i++) {
2036 const char* localeStr = locales[i].string();
2037 if (localeStr == NULL || strlen(localeStr) == 0) {
2038 localeStr = "--_--";
2039 }
2040 printf(" '%s'", localeStr);
2041 }
2042 printf("\n");
2043
2044 printf("densities:");
2045 const size_t ND = densities.size();
2046 for (size_t i=0; i<ND; i++) {
2047 printf(" '%d'", densities[i]);
2048 }
2049 printf("\n");
2050
2051 AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
2052 if (dir != NULL) {
2053 if (dir->getFileCount() > 0) {
2054 printf("native-code:");
2055 for (size_t i=0; i<dir->getFileCount(); i++) {
Maurice Chu2675f762013-10-22 17:33:11 -07002056 printf(" '%s'", ResTable::normalizeForOutput(
2057 dir->getFileName(i).string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002058 }
2059 printf("\n");
2060 }
2061 delete dir;
2062 }
2063 } else if (strcmp("badger", option) == 0) {
2064 printf("%s", CONSOLE_DATA);
2065 } else if (strcmp("configurations", option) == 0) {
2066 Vector<ResTable_config> configs;
2067 res.getConfigurations(&configs);
2068 const size_t N = configs.size();
2069 for (size_t i=0; i<N; i++) {
2070 printf("%s\n", configs[i].toString().string());
2071 }
2072 } else {
2073 fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
2074 goto bail;
2075 }
2076 }
2077
2078 result = NO_ERROR;
2079
2080bail:
2081 if (asset) {
2082 delete asset;
2083 }
2084 return (result != NO_ERROR);
2085}
2086
2087
2088/*
2089 * Handle the "add" command, which wants to add files to a new or
2090 * pre-existing archive.
2091 */
2092int doAdd(Bundle* bundle)
2093{
2094 ZipFile* zip = NULL;
2095 status_t result = UNKNOWN_ERROR;
2096 const char* zipFileName;
2097
2098 if (bundle->getUpdate()) {
2099 /* avoid confusion */
2100 fprintf(stderr, "ERROR: can't use '-u' with add\n");
2101 goto bail;
2102 }
2103
2104 if (bundle->getFileSpecCount() < 1) {
2105 fprintf(stderr, "ERROR: must specify zip file name\n");
2106 goto bail;
2107 }
2108 zipFileName = bundle->getFileSpecEntry(0);
2109
2110 if (bundle->getFileSpecCount() < 2) {
2111 fprintf(stderr, "NOTE: nothing to do\n");
2112 goto bail;
2113 }
2114
2115 zip = openReadWrite(zipFileName, true);
2116 if (zip == NULL) {
2117 fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
2118 goto bail;
2119 }
2120
2121 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2122 const char* fileName = bundle->getFileSpecEntry(i);
2123
2124 if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
2125 printf(" '%s'... (from gzip)\n", fileName);
2126 result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
2127 } else {
2128 if (bundle->getJunkPath()) {
2129 String8 storageName = String8(fileName).getPathLeaf();
Maurice Chu2675f762013-10-22 17:33:11 -07002130 printf(" '%s' as '%s'...\n", fileName,
2131 ResTable::normalizeForOutput(storageName.string()).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002132 result = zip->add(fileName, storageName.string(),
2133 bundle->getCompressionMethod(), NULL);
2134 } else {
2135 printf(" '%s'...\n", fileName);
2136 result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
2137 }
2138 }
2139 if (result != NO_ERROR) {
2140 fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
2141 if (result == NAME_NOT_FOUND) {
2142 fprintf(stderr, ": file not found\n");
2143 } else if (result == ALREADY_EXISTS) {
2144 fprintf(stderr, ": already exists in archive\n");
2145 } else {
2146 fprintf(stderr, "\n");
2147 }
2148 goto bail;
2149 }
2150 }
2151
2152 result = NO_ERROR;
2153
2154bail:
2155 delete zip;
2156 return (result != NO_ERROR);
2157}
2158
2159
2160/*
2161 * Delete files from an existing archive.
2162 */
2163int doRemove(Bundle* bundle)
2164{
2165 ZipFile* zip = NULL;
2166 status_t result = UNKNOWN_ERROR;
2167 const char* zipFileName;
2168
2169 if (bundle->getFileSpecCount() < 1) {
2170 fprintf(stderr, "ERROR: must specify zip file name\n");
2171 goto bail;
2172 }
2173 zipFileName = bundle->getFileSpecEntry(0);
2174
2175 if (bundle->getFileSpecCount() < 2) {
2176 fprintf(stderr, "NOTE: nothing to do\n");
2177 goto bail;
2178 }
2179
2180 zip = openReadWrite(zipFileName, false);
2181 if (zip == NULL) {
2182 fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
2183 zipFileName);
2184 goto bail;
2185 }
2186
2187 for (int i = 1; i < bundle->getFileSpecCount(); i++) {
2188 const char* fileName = bundle->getFileSpecEntry(i);
2189 ZipEntry* entry;
2190
2191 entry = zip->getEntryByName(fileName);
2192 if (entry == NULL) {
2193 printf(" '%s' NOT FOUND\n", fileName);
2194 continue;
2195 }
2196
2197 result = zip->remove(entry);
2198
2199 if (result != NO_ERROR) {
2200 fprintf(stderr, "Unable to delete '%s' from '%s'\n",
2201 bundle->getFileSpecEntry(i), zipFileName);
2202 goto bail;
2203 }
2204 }
2205
2206 /* update the archive */
2207 zip->flush();
2208
2209bail:
2210 delete zip;
2211 return (result != NO_ERROR);
2212}
2213
Adam Lesinski3921e872014-05-13 10:56:25 -07002214static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder, bool ignoreConfig=false) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002215 const size_t numDirs = dir->getDirs().size();
2216 for (size_t i = 0; i < numDirs; i++) {
Adam Lesinski3921e872014-05-13 10:56:25 -07002217 bool ignore = ignoreConfig;
2218 const sp<AaptDir>& subDir = dir->getDirs().valueAt(i);
2219 const char* dirStr = subDir->getLeaf().string();
2220 if (!ignore && strstr(dirStr, "mipmap") == dirStr) {
2221 ignore = true;
2222 }
2223 status_t err = addResourcesToBuilder(subDir, builder, ignore);
Adam Lesinskifab50872014-04-16 14:40:42 -07002224 if (err != NO_ERROR) {
2225 return err;
2226 }
2227 }
2228
2229 const size_t numFiles = dir->getFiles().size();
2230 for (size_t i = 0; i < numFiles; i++) {
2231 sp<AaptGroup> gp = dir->getFiles().valueAt(i);
2232 const size_t numConfigs = gp->getFiles().size();
2233 for (size_t j = 0; j < numConfigs; j++) {
Adam Lesinski3921e872014-05-13 10:56:25 -07002234 status_t err = NO_ERROR;
2235 if (ignoreConfig) {
2236 err = builder->getBaseSplit()->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2237 } else {
2238 err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
2239 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002240 if (err != NO_ERROR) {
2241 fprintf(stderr, "Failed to add %s (%s) to builder.\n",
2242 gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string());
2243 return err;
2244 }
2245 }
2246 }
2247 return NO_ERROR;
2248}
2249
2250static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) {
2251 if (split->isBase()) {
2252 return original;
2253 }
2254
2255 String8 ext(original.getPathExtension());
2256 if (ext == String8(".apk")) {
2257 return String8::format("%s_%s%s",
2258 original.getBasePath().string(),
2259 split->getDirectorySafeName().string(),
2260 ext.string());
2261 }
2262
2263 return String8::format("%s_%s", original.string(),
2264 split->getDirectorySafeName().string());
2265}
Adam Lesinski282e1812014-01-23 18:17:42 -08002266
2267/*
2268 * Package up an asset directory and associated application files.
2269 */
2270int doPackage(Bundle* bundle)
2271{
2272 const char* outputAPKFile;
2273 int retVal = 1;
2274 status_t err;
2275 sp<AaptAssets> assets;
2276 int N;
2277 FILE* fp;
2278 String8 dependencyFile;
Adam Lesinskifab50872014-04-16 14:40:42 -07002279 sp<ApkBuilder> builder;
Adam Lesinski282e1812014-01-23 18:17:42 -08002280
Anton Krumina2ef5c02014-03-12 14:46:44 -07002281 // -c en_XA or/and ar_XB means do pseudolocalization
Adam Lesinskifab50872014-04-16 14:40:42 -07002282 sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
2283 err = configFilter->parse(bundle->getConfigurations());
Adam Lesinski282e1812014-01-23 18:17:42 -08002284 if (err != NO_ERROR) {
2285 goto bail;
2286 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002287 if (configFilter->containsPseudo()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002288 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
2289 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002290 if (configFilter->containsPseudoBidi()) {
Anton Krumina2ef5c02014-03-12 14:46:44 -07002291 bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
Adam Lesinski282e1812014-01-23 18:17:42 -08002292 }
2293
2294 N = bundle->getFileSpecCount();
2295 if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
Adam Lesinski09384302014-01-22 16:07:42 -08002296 && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002297 fprintf(stderr, "ERROR: no input files\n");
2298 goto bail;
2299 }
2300
2301 outputAPKFile = bundle->getOutputAPKFile();
2302
2303 // Make sure the filenames provided exist and are of the appropriate type.
2304 if (outputAPKFile) {
2305 FileType type;
2306 type = getFileType(outputAPKFile);
2307 if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
2308 fprintf(stderr,
2309 "ERROR: output file '%s' exists but is not regular file\n",
2310 outputAPKFile);
2311 goto bail;
2312 }
2313 }
2314
2315 // Load the assets.
2316 assets = new AaptAssets();
2317
2318 // Set up the resource gathering in assets if we're going to generate
2319 // dependency files. Every time we encounter a resource while slurping
2320 // the tree, we'll add it to these stores so we have full resource paths
2321 // to write to a dependency file.
2322 if (bundle->getGenDependencies()) {
2323 sp<FilePathStore> resPathStore = new FilePathStore;
2324 assets->setFullResPaths(resPathStore);
2325 sp<FilePathStore> assetPathStore = new FilePathStore;
2326 assets->setFullAssetPaths(assetPathStore);
2327 }
2328
2329 err = assets->slurpFromArgs(bundle);
2330 if (err < 0) {
2331 goto bail;
2332 }
2333
2334 if (bundle->getVerbose()) {
2335 assets->print(String8());
2336 }
2337
Adam Lesinskifab50872014-04-16 14:40:42 -07002338 // Create the ApkBuilder, which will collect the compiled files
2339 // to write to the final APK (or sets of APKs if we are building
2340 // a Split APK.
2341 builder = new ApkBuilder(configFilter);
2342
2343 // If we are generating a Split APK, find out which configurations to split on.
2344 if (bundle->getSplitConfigurations().size() > 0) {
2345 const Vector<String8>& splitStrs = bundle->getSplitConfigurations();
2346 const size_t numSplits = splitStrs.size();
2347 for (size_t i = 0; i < numSplits; i++) {
2348 std::set<ConfigDescription> configs;
2349 if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {
2350 fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());
2351 goto bail;
2352 }
2353
2354 err = builder->createSplitForConfigs(configs);
2355 if (err != NO_ERROR) {
2356 goto bail;
2357 }
2358 }
2359 }
2360
Adam Lesinski282e1812014-01-23 18:17:42 -08002361 // If they asked for any fileAs that need to be compiled, do so.
2362 if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002363 err = buildResources(bundle, assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002364 if (err != 0) {
2365 goto bail;
2366 }
2367 }
2368
2369 // At this point we've read everything and processed everything. From here
2370 // on out it's just writing output files.
2371 if (SourcePos::hasErrors()) {
2372 goto bail;
2373 }
2374
2375 // Update symbols with information about which ones are needed as Java symbols.
2376 assets->applyJavaSymbols();
2377 if (SourcePos::hasErrors()) {
2378 goto bail;
2379 }
2380
2381 // If we've been asked to generate a dependency file, do that here
2382 if (bundle->getGenDependencies()) {
2383 // If this is the packaging step, generate the dependency file next to
2384 // the output apk (e.g. bin/resources.ap_.d)
2385 if (outputAPKFile) {
2386 dependencyFile = String8(outputAPKFile);
2387 // Add the .d extension to the dependency file.
2388 dependencyFile.append(".d");
2389 } else {
2390 // Else if this is the R.java dependency generation step,
2391 // generate the dependency file in the R.java package subdirectory
2392 // e.g. gen/com/foo/app/R.java.d
2393 dependencyFile = String8(bundle->getRClassDir());
2394 dependencyFile.appendPath("R.java.d");
2395 }
2396 // Make sure we have a clean dependency file to start with
2397 fp = fopen(dependencyFile, "w");
2398 fclose(fp);
2399 }
2400
2401 // Write out R.java constants
2402 if (!assets->havePrivateSymbols()) {
2403 if (bundle->getCustomPackage() == NULL) {
2404 // Write the R.java file into the appropriate class directory
2405 // e.g. gen/com/foo/app/R.java
2406 err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
2407 } else {
2408 const String8 customPkg(bundle->getCustomPackage());
2409 err = writeResourceSymbols(bundle, assets, customPkg, true);
2410 }
2411 if (err < 0) {
2412 goto bail;
2413 }
2414 // If we have library files, we're going to write our R.java file into
2415 // the appropriate class directory for those libraries as well.
2416 // e.g. gen/com/foo/app/lib/R.java
2417 if (bundle->getExtraPackages() != NULL) {
2418 // Split on colon
2419 String8 libs(bundle->getExtraPackages());
2420 char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
2421 while (packageString != NULL) {
2422 // Write the R.java file out with the correct package name
2423 err = writeResourceSymbols(bundle, assets, String8(packageString), true);
2424 if (err < 0) {
2425 goto bail;
2426 }
2427 packageString = strtok(NULL, ":");
2428 }
2429 libs.unlockBuffer();
2430 }
2431 } else {
2432 err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
2433 if (err < 0) {
2434 goto bail;
2435 }
2436 err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
2437 if (err < 0) {
2438 goto bail;
2439 }
2440 }
2441
2442 // Write out the ProGuard file
2443 err = writeProguardFile(bundle, assets);
2444 if (err < 0) {
2445 goto bail;
2446 }
2447
2448 // Write the apk
2449 if (outputAPKFile) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002450 // Gather all resources and add them to the APK Builder. The builder will then
2451 // figure out which Split they belong in.
2452 err = addResourcesToBuilder(assets, builder);
Adam Lesinski282e1812014-01-23 18:17:42 -08002453 if (err != NO_ERROR) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002454 goto bail;
2455 }
Adam Lesinskifab50872014-04-16 14:40:42 -07002456
2457 const Vector<sp<ApkSplit> >& splits = builder->getSplits();
2458 const size_t numSplits = splits.size();
2459 for (size_t i = 0; i < numSplits; i++) {
2460 const sp<ApkSplit>& split = splits[i];
2461 String8 outputPath = buildApkName(String8(outputAPKFile), split);
2462 err = writeAPK(bundle, outputPath, split);
2463 if (err != NO_ERROR) {
2464 fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
2465 goto bail;
2466 }
2467 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002468 }
2469
2470 // If we've been asked to generate a dependency file, we need to finish up here.
2471 // the writeResourceSymbols and writeAPK functions have already written the target
2472 // half of the dependency file, now we need to write the prerequisites. (files that
2473 // the R.java file or .ap_ file depend on)
2474 if (bundle->getGenDependencies()) {
2475 // Now that writeResourceSymbols or writeAPK has taken care of writing
2476 // the targets to our dependency file, we'll write the prereqs
2477 fp = fopen(dependencyFile, "a+");
2478 fprintf(fp, " : ");
2479 bool includeRaw = (outputAPKFile != NULL);
2480 err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
2481 // Also manually add the AndroidManifeset since it's not under res/ or assets/
2482 // and therefore was not added to our pathstores during slurping
2483 fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
2484 fclose(fp);
2485 }
2486
2487 retVal = 0;
2488bail:
2489 if (SourcePos::hasErrors()) {
2490 SourcePos::printErrors(stderr);
2491 }
2492 return retVal;
2493}
2494
2495/*
2496 * Do PNG Crunching
2497 * PRECONDITIONS
2498 * -S flag points to a source directory containing drawable* folders
2499 * -C flag points to destination directory. The folder structure in the
2500 * source directory will be mirrored to the destination (cache) directory
2501 *
2502 * POSTCONDITIONS
2503 * Destination directory will be updated to match the PNG files in
Maurice Chu2675f762013-10-22 17:33:11 -07002504 * the source directory.
Adam Lesinski282e1812014-01-23 18:17:42 -08002505 */
2506int doCrunch(Bundle* bundle)
2507{
2508 fprintf(stdout, "Crunching PNG Files in ");
2509 fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
2510 fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
2511
2512 updatePreProcessedCache(bundle);
2513
2514 return NO_ERROR;
2515}
2516
2517/*
2518 * Do PNG Crunching on a single flag
2519 * -i points to a single png file
2520 * -o points to a single png output file
2521 */
2522int doSingleCrunch(Bundle* bundle)
2523{
2524 fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile());
2525 fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile());
2526
2527 String8 input(bundle->getSingleCrunchInputFile());
2528 String8 output(bundle->getSingleCrunchOutputFile());
2529
2530 if (preProcessImageToCache(bundle, input, output) != NO_ERROR) {
2531 // we can't return the status_t as it gets truncate to the lower 8 bits.
2532 return 42;
2533 }
2534
2535 return NO_ERROR;
2536}
2537
2538char CONSOLE_DATA[2925] = {
2539 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2540 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2541 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2542 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2543 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63,
2544 86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83,
2545 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2546 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2547 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81,
2548 81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32,
2549 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2550 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2551 32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59,
2552 59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2553 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2554 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59,
2555 59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32,
2556 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2557 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2558 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87,
2559 58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32,
2560 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2561 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2562 47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58,
2563 121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2564 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2565 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81,
2566 81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32,
2567 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2568 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2569 32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59,
2570 59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2571 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2572 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59,
2573 59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32,
2574 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2575 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2576 32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81,
2577 70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32,
2578 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2579 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2580 32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81,
2581 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2582 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2583 32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109,
2584 81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32,
2585 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2586 32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59,
2587 59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59,
2588 60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2589 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59,
2590 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46,
2591 61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32,
2592 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2593 32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2594 59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81,
2595 109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32,
2596 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81,
2597 67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2598 59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58,
2599 61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32,
2600 32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59,
2601 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37,
2602 73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59,
2603 59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2604 32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59,
2605 59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96,
2606 46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61,
2607 97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32,
2608 46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61,
2609 119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59,
2610 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32,
2611 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2612 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119,
2613 119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59,
2614 59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10,
2615 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2616 32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81,
2617 81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41,
2618 87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2619 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81,
2620 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58,
2621 45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32,
2622 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2623 32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2624 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32,
2625 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2626 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58,
2627 59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58,
2628 59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2629 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2630 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81,
2631 81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32,
2632 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2633 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2634 32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2635 81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2636 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
2637 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106,
2638 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59,
2639 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2640 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2641 32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81,
2642 81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32,
2643 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
2644 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2645 58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59,
2646 61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2647 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2648 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81,
2649 81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32,
2650 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2651 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2652 32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81,
2653 81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2654 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
2655 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59,
2656 59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59,
2657 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2658 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2659 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33,
2660 58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32,
2661 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
2662 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
2663 61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59,
2664 59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32,
2665 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32,
2666 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95,
2667 32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59,
2668 61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2669 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2670 32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45,
2671 59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32,
2672 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32,
2673 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59,
2674 59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59,
2675 59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2676 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2677 32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2678 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32,
2679 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
2680 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2681 46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61,
2682 46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2683 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
2684 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59,
2685 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59,
2686 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2687 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2688 32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32,
2689 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32,
2690 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
2691 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61,
2692 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59,
2693 59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2694 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2695 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32,
2696 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32,
2697 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2698 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2699 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2700 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
2701 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10
2702 };