diff options
Diffstat (limited to 'tools/aapt/Command.cpp')
-rw-r--r-- | tools/aapt/Command.cpp | 1477 |
1 files changed, 939 insertions, 538 deletions
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index f8633112c018..0b11c9323c19 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -3,31 +3,41 @@ // // Android Asset Packaging Tool main entry point. // -#include "Main.h" +#include "AaptXml.h" +#include "ApkBuilder.h" #include "Bundle.h" +#include "Images.h" +#include "Main.h" #include "ResourceFilter.h" #include "ResourceTable.h" -#include "Images.h" #include "XMLNode.h" +#include <utils/Errors.h> +#include <utils/KeyedVector.h> +#include <utils/List.h> #include <utils/Log.h> +#include <utils/SortedVector.h> #include <utils/threads.h> -#include <utils/List.h> -#include <utils/Errors.h> +#include <utils/Vector.h> -#include <fcntl.h> #include <errno.h> +#include <fcntl.h> using namespace android; +#ifndef AAPT_VERSION + #define AAPT_VERSION "" +#endif + /* * Show version info. All the cool kids do it. */ int doVersion(Bundle* bundle) { - if (bundle->getFileSpecCount() != 0) + if (bundle->getFileSpecCount() != 0) { printf("(ignoring extra arguments)\n"); - printf("Android Asset Packaging Tool, v0.2\n"); + } + printf("Android Asset Packaging Tool, v0.2-" AAPT_VERSION "\n"); return 0; } @@ -46,13 +56,14 @@ ZipFile* openReadOnly(const char* fileName) zip = new ZipFile; result = zip->open(fileName, ZipFile::kOpenReadOnly); if (result != NO_ERROR) { - if (result == NAME_NOT_FOUND) + if (result == NAME_NOT_FOUND) { fprintf(stderr, "ERROR: '%s' not found\n", fileName); - else if (result == PERMISSION_DENIED) + } else if (result == PERMISSION_DENIED) { fprintf(stderr, "ERROR: '%s' access denied\n", fileName); - else + } else { fprintf(stderr, "ERROR: failed opening '%s' as Zip file\n", fileName); + } delete zip; return NULL; } @@ -73,8 +84,9 @@ ZipFile* openReadWrite(const char* fileName, bool okayToCreate) int flags; flags = ZipFile::kOpenReadWrite; - if (okayToCreate) + if (okayToCreate) { flags |= ZipFile::kOpenCreate; + } zip = new ZipFile; result = zip->open(fileName, flags); @@ -94,12 +106,13 @@ bail: */ const char* compressionName(int method) { - if (method == ZipEntry::kCompressStored) + if (method == ZipEntry::kCompressStored) { return "Stored"; - else if (method == ZipEntry::kCompressDeflated) + } else if (method == ZipEntry::kCompressDeflated) { return "Deflated"; - else + } else { return "Unknown"; + } } /* @@ -107,10 +120,11 @@ const char* compressionName(int method) */ int calcPercent(long uncompressedLen, long compressedLen) { - if (!uncompressedLen) + if (!uncompressedLen) { return 0; - else + } else { return (int) (100.0 - (compressedLen * 100.0) / uncompressedLen + 0.5); + } } /* @@ -135,8 +149,9 @@ int doList(Bundle* bundle) zipFileName = bundle->getFileSpecEntry(0); zip = openReadOnly(zipFileName); - if (zip == NULL) + if (zip == NULL) { goto bail; + } int count, i; @@ -223,117 +238,25 @@ bail: return result; } -static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes) -{ - size_t N = tree.getAttributeCount(); - for (size_t i=0; i<N; i++) { - if (tree.getAttributeNameResID(i) == attrRes) { - return (ssize_t)i; - } - } - return -1; -} - -String8 getAttribute(const ResXMLTree& tree, const char* ns, - const char* attr, String8* outError) -{ - ssize_t idx = tree.indexOfAttribute(ns, attr); - if (idx < 0) { - return String8(); - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType != Res_value::TYPE_STRING) { - if (outError != NULL) *outError = "attribute is not a string value"; - return String8(); - } - } - size_t len; - const char16_t* str = tree.getAttributeStringValue(idx, &len); - return str ? String8(str, len) : String8(); -} - -static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return String8(); - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType != Res_value::TYPE_STRING) { - if (outError != NULL) *outError = "attribute is not a string value"; - return String8(); - } - } - size_t len; - const char16_t* str = tree.getAttributeStringValue(idx, &len); - return str ? String8(str, len) : String8(); -} - -static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes, - String8* outError, int32_t defValue = -1) +static void printResolvedResourceAttribute(const ResTable& resTable, const ResXMLTree& tree, + uint32_t attrRes, String8 attrLabel, String8* outError) { - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return defValue; - } Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType < Res_value::TYPE_FIRST_INT - || value.dataType > Res_value::TYPE_LAST_INT) { - if (outError != NULL) *outError = "attribute is not an integer value"; - return defValue; - } - } - return value.data; -} - -static int32_t getResolvedIntegerAttribute(const ResTable* resTable, const ResXMLTree& tree, - uint32_t attrRes, String8* outError, int32_t defValue = -1) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return defValue; - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType == Res_value::TYPE_REFERENCE) { - resTable->resolveReference(&value, 0); - } - if (value.dataType < Res_value::TYPE_FIRST_INT - || value.dataType > Res_value::TYPE_LAST_INT) { - if (outError != NULL) *outError = "attribute is not an integer value"; - return defValue; - } + AaptXml::getResolvedResourceAttribute(resTable, tree, attrRes, &value, outError); + if (*outError != "") { + *outError = "error print resolved resource attribute"; + return; } - return value.data; -} - -static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree, - uint32_t attrRes, String8* outError) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return String8(); - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType == Res_value::TYPE_STRING) { - size_t len; - const char16_t* str = tree.getAttributeStringValue(idx, &len); - return str ? String8(str, len) : String8(); - } - resTable->resolveReference(&value, 0); - if (value.dataType != Res_value::TYPE_STRING) { - if (outError != NULL) *outError = "attribute is not a string value"; - return String8(); - } + if (value.dataType == Res_value::TYPE_STRING) { + String8 result = AaptXml::getResolvedAttribute(resTable, tree, attrRes, outError); + printf("%s='%s'", attrLabel.string(), + ResTable::normalizeForOutput(result.string()).string()); + } else if (Res_value::TYPE_FIRST_INT <= value.dataType && + value.dataType <= Res_value::TYPE_LAST_INT) { + printf("%s='%d'", attrLabel.string(), value.data); + } else { + printf("%s='0x%x'", attrLabel.string(), (int)value.data); } - size_t len; - const Res_value* value2 = &value; - const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len); - return str ? String8(str, len) : String8(); } // These are attribute resource constants for the platform, as found @@ -343,8 +266,11 @@ enum { ICON_ATTR = 0x01010002, NAME_ATTR = 0x01010003, PERMISSION_ATTR = 0x01010006, + EXPORTED_ATTR = 0x01010010, + GRANT_URI_PERMISSIONS_ATTR = 0x0101001b, RESOURCE_ATTR = 0x01010025, DEBUGGABLE_ATTR = 0x0101000f, + VALUE_ATTR = 0x01010024, VERSION_CODE_ATTR = 0x0101021b, VERSION_NAME_ATTR = 0x0101021c, SCREEN_ORIENTATION_ATTR = 0x0101001e, @@ -364,6 +290,7 @@ enum { LARGE_SCREEN_ATTR = 0x01010286, XLARGE_SCREEN_ATTR = 0x010102bf, REQUIRED_ATTR = 0x0101028e, + INSTALL_LOCATION_ATTR = 0x010102b7, SCREEN_SIZE_ATTR = 0x010102ca, SCREEN_DENSITY_ATTR = 0x010102cb, REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364, @@ -371,9 +298,10 @@ enum { LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366, PUBLIC_KEY_ATTR = 0x010103a6, CATEGORY_ATTR = 0x010103e8, + BANNER_ATTR = 0x10103f2, }; -const char *getComponentName(String8 &pkgName, String8 &componentName) { +String8 getComponentName(String8 &pkgName, String8 &componentName) { ssize_t idx = componentName.find("."); String8 retStr(pkgName); if (idx == 0) { @@ -382,12 +310,12 @@ const char *getComponentName(String8 &pkgName, String8 &componentName) { retStr += "."; retStr += componentName; } else { - return componentName.string(); + return componentName; } - return retStr.string(); + return retStr; } -static void printCompatibleScreens(ResXMLTree& tree) { +static void printCompatibleScreens(ResXMLTree& tree, String8* outError) { size_t len; ResXMLTree::event_code_t code; int depth = 0; @@ -405,12 +333,17 @@ static void printCompatibleScreens(ResXMLTree& tree) { continue; } depth++; - String8 tag(tree.getElementName(&len)); + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + *outError = "failed to get XML element name (bad string pool)"; + return; + } + String8 tag(ctag16); if (tag == "screen") { - int32_t screenSize = getIntegerAttribute(tree, - SCREEN_SIZE_ATTR, NULL, -1); - int32_t screenDensity = getIntegerAttribute(tree, - SCREEN_DENSITY_ATTR, NULL, -1); + int32_t screenSize = AaptXml::getIntegerAttribute(tree, + SCREEN_SIZE_ATTR); + int32_t screenDensity = AaptXml::getIntegerAttribute(tree, + SCREEN_DENSITY_ATTR); if (screenSize > 0 && screenDensity > 0) { if (!first) { printf(","); @@ -423,6 +356,29 @@ static void printCompatibleScreens(ResXMLTree& tree) { printf("\n"); } +static void printUsesPermission(const String8& name, bool optional=false, int maxSdkVersion=-1) { + printf("uses-permission: name='%s'", ResTable::normalizeForOutput(name.string()).string()); + if (maxSdkVersion != -1) { + printf(" maxSdkVersion='%d'", maxSdkVersion); + } + printf("\n"); + + if (optional) { + printf("optional-permission: name='%s'", + ResTable::normalizeForOutput(name.string()).string()); + if (maxSdkVersion != -1) { + printf(" maxSdkVersion='%d'", maxSdkVersion); + } + printf("\n"); + } +} + +static void printUsesImpliedPermission(const String8& name, const String8& reason) { + printf("uses-implied-permission: name='%s' reason='%s'\n", + ResTable::normalizeForOutput(name.string()).string(), + ResTable::normalizeForOutput(reason.string()).string()); +} + Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool offHost, String8 *outError = NULL) { @@ -447,7 +403,12 @@ Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::END_TAG) { depth--; - String8 tag(tree.getElementName(&len)); + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + *outError = "failed to get XML element name (bad string pool)"; + return Vector<String8>(); + } + String8 tag(ctag16); if (depth == 0 && tag == serviceTagName) { withinApduService = false; @@ -455,7 +416,12 @@ Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool } else if (code == ResXMLTree::START_TAG) { depth++; - String8 tag(tree.getElementName(&len)); + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + *outError = "failed to get XML element name (bad string pool)"; + return Vector<String8>(); + } + String8 tag(ctag16); if (depth == 1) { if (tag == serviceTagName) { @@ -463,7 +429,7 @@ Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool } } else if (depth == 2 && withinApduService) { if (tag == "aid-group") { - String8 category = getAttribute(tree, CATEGORY_ATTR, &error); + String8 category = AaptXml::getAttribute(tree, CATEGORY_ATTR, &error); if (error != "") { if (outError != NULL) *outError = error; return Vector<String8>(); @@ -478,6 +444,126 @@ Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool return categories; } +static void printComponentPresence(const char* componentName) { + printf("provides-component:'%s'\n", componentName); +} + +/** + * Represents a feature that has been automatically added due to + * a pre-requisite or some other reason. + */ +struct ImpliedFeature { + /** + * Name of the implied feature. + */ + String8 name; + + /** + * List of human-readable reasons for why this feature was implied. + */ + SortedVector<String8> reasons; +}; + +/** + * Represents a <feature-group> tag in the AndroidManifest.xml + */ +struct FeatureGroup { + FeatureGroup() : openGLESVersion(-1) {} + + /** + * Human readable label + */ + String8 label; + + /** + * Explicit features defined in the group + */ + KeyedVector<String8, bool> features; + + /** + * OpenGL ES version required + */ + int openGLESVersion; +}; + +static void addImpliedFeature(KeyedVector<String8, ImpliedFeature>* impliedFeatures, + const char* name, const char* reason) { + String8 name8(name); + ssize_t idx = impliedFeatures->indexOfKey(name8); + if (idx < 0) { + idx = impliedFeatures->add(name8, ImpliedFeature()); + impliedFeatures->editValueAt(idx).name = name8; + } + impliedFeatures->editValueAt(idx).reasons.add(String8(reason)); +} + +static void printFeatureGroup(const FeatureGroup& grp, + const KeyedVector<String8, ImpliedFeature>* impliedFeatures = NULL) { + printf("feature-group: label='%s'\n", grp.label.string()); + + if (grp.openGLESVersion > 0) { + printf(" uses-gl-es: '0x%x'\n", grp.openGLESVersion); + } + + const size_t numFeatures = grp.features.size(); + for (size_t i = 0; i < numFeatures; i++) { + if (!grp.features[i]) { + continue; + } + + const String8& featureName = grp.features.keyAt(i); + printf(" uses-feature: name='%s'\n", + ResTable::normalizeForOutput(featureName.string()).string()); + } + + const size_t numImpliedFeatures = + (impliedFeatures != NULL) ? impliedFeatures->size() : 0; + for (size_t i = 0; i < numImpliedFeatures; i++) { + const ImpliedFeature& impliedFeature = impliedFeatures->valueAt(i); + if (grp.features.indexOfKey(impliedFeature.name) >= 0) { + // The feature is explicitly set, no need to use implied + // definition. + continue; + } + + String8 printableFeatureName(ResTable::normalizeForOutput( + impliedFeature.name.string())); + printf(" uses-feature: name='%s'\n", printableFeatureName.string()); + printf(" uses-implied-feature: name='%s' reason='", + printableFeatureName.string()); + const size_t numReasons = impliedFeature.reasons.size(); + for (size_t j = 0; j < numReasons; j++) { + printf("%s", impliedFeature.reasons[j].string()); + if (j + 2 < numReasons) { + printf(", "); + } else if (j + 1 < numReasons) { + printf(", and "); + } + } + printf("'\n"); + } +} + +static void addParentFeatures(FeatureGroup* grp, const String8& name) { + if (name == "android.hardware.camera.autofocus" || + name == "android.hardware.camera.flash") { + grp->features.add(String8("android.hardware.camera"), true); + } else if (name == "android.hardware.location.gps" || + name == "android.hardware.location.network") { + grp->features.add(String8("android.hardware.location"), true); + } else if (name == "android.hardware.touchscreen.multitouch") { + grp->features.add(String8("android.hardware.touchscreen"), true); + } else if (name == "android.hardware.touchscreen.multitouch.distinct") { + grp->features.add(String8("android.hardware.touchscreen.multitouch"), true); + grp->features.add(String8("android.hardware.touchscreen"), true); + } else if (name == "android.hardware.opengles.aep") { + const int openGLESVersion31 = 0x00030001; + if (openGLESVersion31 > grp->openGLESVersion) { + grp->openGLESVersion = openGLESVersion31; + } + } +} + /* * Handle the "dump" command, to extract select data from an archive. */ @@ -485,7 +571,6 @@ extern char CONSOLE_DATA[2925]; // see EOF int doDump(Bundle* bundle) { status_t result = UNKNOWN_ERROR; - Asset* asset = NULL; if (bundle->getFileSpecCount() < 1) { fprintf(stderr, "ERROR: no dump option specified\n"); @@ -524,9 +609,21 @@ int doDump(Bundle* bundle) config.screenWidthDp = 320; config.screenHeightDp = 480; config.smallestScreenWidthDp = 320; + config.screenLayout |= ResTable_config::SCREENSIZE_NORMAL; assets.setConfiguration(config); const ResTable& res = assets.getResources(false); + if (res.getError() != NO_ERROR) { + fprintf(stderr, "ERROR: dump failed because the resource table is invalid/corrupt.\n"); + return 1; + } + + // The dynamicRefTable can be null if there are no resources for this asset cookie. + // This fine. + const DynamicRefTable* dynamicRefTable = res.getDynamicRefTableForCookie(assetsCookie); + + Asset* asset = NULL; + if (strcmp("resources", option) == 0) { #ifndef HAVE_ANDROID_OS res.print(bundle->getValues()); @@ -544,8 +641,8 @@ int doDump(Bundle* bundle) for (int i=2; i<bundle->getFileSpecCount(); i++) { const char* resname = bundle->getFileSpecEntry(i); - ResXMLTree tree; - asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER); + ResXMLTree tree(dynamicRefTable); + asset = assets.openNonAsset(assetsCookie, resname, Asset::ACCESS_BUFFER); if (asset == NULL) { fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname); goto bail; @@ -571,13 +668,13 @@ int doDump(Bundle* bundle) for (int i=2; i<bundle->getFileSpecCount(); i++) { const char* resname = bundle->getFileSpecEntry(i); - ResXMLTree tree; - asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER); + asset = assets.openNonAsset(assetsCookie, resname, Asset::ACCESS_BUFFER); if (asset == NULL) { fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname); goto bail; } + ResXMLTree tree(dynamicRefTable); if (tree.setTo(asset->getBuffer(true), asset->getLength()) != NO_ERROR) { fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname); @@ -589,14 +686,13 @@ int doDump(Bundle* bundle) } } else { - ResXMLTree tree; - asset = assets.openNonAsset("AndroidManifest.xml", - Asset::ACCESS_BUFFER); + asset = assets.openNonAsset(assetsCookie, "AndroidManifest.xml", Asset::ACCESS_BUFFER); if (asset == NULL) { fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n"); goto bail; } + ResXMLTree tree(dynamicRefTable); if (tree.setTo(asset->getBuffer(true), asset->getLength()) != NO_ERROR) { fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n"); @@ -617,35 +713,39 @@ int doDump(Bundle* bundle) continue; } depth++; - String8 tag(tree.getElementName(&len)); + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n"); + goto bail; + } + String8 tag(ctag16); //printf("Depth %d tag %s\n", depth, tag.string()); if (depth == 1) { if (tag != "manifest") { fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); goto bail; } - String8 pkg = getAttribute(tree, NULL, "package", NULL); - printf("package: %s\n", pkg.string()); + String8 pkg = AaptXml::getAttribute(tree, NULL, "package", NULL); + printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string()); } else if (depth == 2 && tag == "permission") { String8 error; - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR: %s\n", error.string()); goto bail; } - printf("permission: %s\n", name.string()); + printf("permission: %s\n", + ResTable::normalizeForOutput(name.string()).string()); } else if (depth == 2 && tag == "uses-permission") { String8 error; - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR: %s\n", error.string()); goto bail; } - printf("uses-permission: %s\n", name.string()); - int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1); - if (!req) { - printf("optional-permission: %s\n", name.string()); - } + printUsesPermission(name, + AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, + AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); } } } else if (strcmp("badging", option) == 0) { @@ -658,7 +758,9 @@ int doDump(Bundle* bundle) const size_t NC = configs.size(); for (size_t i=0; i<NC; i++) { int dens = configs[i].density; - if (dens == 0) dens = 160; + if (dens == 0) { + dens = 160; + } densities.add(dens); } @@ -669,24 +771,35 @@ int doDump(Bundle* bundle) bool withinActivity = false; bool isMainActivity = false; bool isLauncherActivity = false; + bool isLeanbackLauncherActivity = false; bool isSearchable = false; bool withinApplication = false; bool withinSupportsInput = false; + bool withinFeatureGroup = false; bool withinReceiver = false; bool withinService = false; + bool withinProvider = false; bool withinIntentFilter = false; bool hasMainActivity = false; bool hasOtherActivities = false; bool hasOtherReceivers = false; bool hasOtherServices = false; + bool hasIntentFilter = false; + bool hasWallpaperService = false; bool hasImeService = false; bool hasAccessibilityService = false; bool hasPrintService = false; bool hasWidgetReceivers = false; bool hasDeviceAdminReceiver = false; - bool hasIntentFilter = false; bool hasPaymentService = false; + bool hasDocumentsProvider = false; + bool hasCameraActivity = false; + bool hasCameraSecureActivity = false; + bool hasLauncher = false; + bool hasNotificationListenerService = false; + bool hasDreamService = false; + bool actMainActivity = false; bool actWidgetReceivers = false; bool actDeviceAdminEnabled = false; @@ -696,6 +809,12 @@ int doDump(Bundle* bundle) bool actPrintService = false; bool actHostApduService = false; bool actOffHostApduService = false; + bool actDocumentsProvider = false; + bool actNotificationListenerService = false; + bool actDreamService = false; + bool actCamera = false; + bool actCameraSecure = false; + bool catLauncher = false; bool hasMetaHostPaymentCategory = false; bool hasMetaOffHostPaymentCategory = false; @@ -706,6 +825,9 @@ int doDump(Bundle* bundle) bool hasBindAccessibilityServicePermission = false; bool hasBindPrintServicePermission = false; bool hasBindNfcServicePermission = false; + bool hasRequiredSafAttributes = false; + bool hasBindNotificationListenerServicePermission = false; + bool hasBindDreamServicePermission = false; // These two implement the implicit permissions that are granted // to pre-1.6 applications. @@ -721,41 +843,16 @@ int doDump(Bundle* bundle) bool hasReadCallLogPermission = false; bool hasWriteCallLogPermission = false; + // If an app declares itself as multiArch, we report the + // native libraries differently. + bool hasMultiArch = false; + // This next group of variables is used to implement a group of // backward-compatibility heuristics necessitated by the addition of // some new uses-feature constants in 2.1 and 2.2. In most cases, the // heuristic is "if an app requests a permission but doesn't explicitly // request the corresponding <uses-feature>, presume it's there anyway". - bool specCameraFeature = false; // camera-related - bool specCameraAutofocusFeature = false; - bool reqCameraAutofocusFeature = false; - bool reqCameraFlashFeature = false; - bool hasCameraPermission = false; - bool specLocationFeature = false; // location-related - bool specNetworkLocFeature = false; - bool reqNetworkLocFeature = false; - bool specGpsFeature = false; - bool reqGpsFeature = false; - bool hasMockLocPermission = false; - bool hasCoarseLocPermission = false; - bool hasGpsPermission = false; - bool hasGeneralLocPermission = false; - bool specBluetoothFeature = false; // Bluetooth API-related - bool hasBluetoothPermission = false; - bool specMicrophoneFeature = false; // microphone-related - bool hasRecordAudioPermission = false; - bool specWiFiFeature = false; - bool hasWiFiPermission = false; - bool specTelephonyFeature = false; // telephony-related - bool reqTelephonySubFeature = false; - bool hasTelephonyPermission = false; - bool specTouchscreenFeature = false; // touchscreen-related - bool specMultitouchFeature = false; - bool reqDistinctMultitouchFeature = false; - bool specScreenPortraitFeature = false; - bool specScreenLandscapeFeature = false; - bool reqScreenPortraitFeature = false; - bool reqScreenLandscapeFeature = false; + // 2.2 also added some other features that apps can request, but that // have no corresponding permission, so we cannot implement any // back-compatibility heuristic for them. The below are thus unnecessary @@ -779,9 +876,15 @@ int doDump(Bundle* bundle) String8 activityName; String8 activityLabel; String8 activityIcon; + String8 activityBanner; String8 receiverName; String8 serviceName; Vector<String8> supportedInput; + + FeatureGroup commonFeatures; + Vector<FeatureGroup> featureGroups; + KeyedVector<String8, ImpliedFeature> impliedFeatures; + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::END_TAG) { depth--; @@ -790,7 +893,8 @@ int doDump(Bundle* bundle) printf("supports-input: '"); const size_t N = supportedInput.size(); for (size_t i=0; i<N; i++) { - printf("%s", supportedInput[i].string()); + printf("%s", ResTable::normalizeForOutput( + supportedInput[i].string()).string()); if (i != N - 1) { printf("' '"); } else { @@ -801,16 +905,31 @@ int doDump(Bundle* bundle) } withinApplication = false; withinSupportsInput = false; + withinFeatureGroup = false; } else if (depth < 3) { - if (withinActivity && isMainActivity && isLauncherActivity) { - const char *aName = getComponentName(pkg, activityName); - printf("launchable-activity:"); - if (aName != NULL) { - printf(" name='%s' ", aName); + if (withinActivity && isMainActivity) { + String8 aName(getComponentName(pkg, activityName)); + if (isLauncherActivity) { + printf("launchable-activity:"); + if (aName.length() > 0) { + printf(" name='%s' ", + ResTable::normalizeForOutput(aName.string()).string()); + } + printf(" label='%s' icon='%s'\n", + ResTable::normalizeForOutput(activityLabel.string()).string(), + ResTable::normalizeForOutput(activityIcon.string()).string()); + } + if (isLeanbackLauncherActivity) { + printf("leanback-launchable-activity:"); + if (aName.length() > 0) { + printf(" name='%s' ", + ResTable::normalizeForOutput(aName.string()).string()); + } + printf(" label='%s' icon='%s' banner='%s'\n", + ResTable::normalizeForOutput(activityLabel.string()).string(), + ResTable::normalizeForOutput(activityIcon.string()).string(), + ResTable::normalizeForOutput(activityBanner.string()).string()); } - printf(" label='%s' icon='%s'\n", - activityLabel.string(), - activityIcon.string()); } if (!hasIntentFilter) { hasOtherActivities |= withinActivity; @@ -827,13 +946,17 @@ int doDump(Bundle* bundle) withinActivity = false; withinService = false; withinReceiver = false; + withinProvider = false; hasIntentFilter = false; - isMainActivity = isLauncherActivity = false; + isMainActivity = isLauncherActivity = isLeanbackLauncherActivity = false; } else if (depth < 4) { if (withinIntentFilter) { if (withinActivity) { hasMainActivity |= actMainActivity; - hasOtherActivities |= !actMainActivity; + hasLauncher |= catLauncher; + hasCameraActivity |= actCamera; + hasCameraSecureActivity |= actCameraSecure; + hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure; } else if (withinReceiver) { hasWidgetReceivers |= actWidgetReceivers; hasDeviceAdminReceiver |= (actDeviceAdminEnabled && @@ -845,9 +968,15 @@ int doDump(Bundle* bundle) hasAccessibilityService |= (actAccessibilityService && hasBindAccessibilityServicePermission); hasPrintService |= (actPrintService && hasBindPrintServicePermission); + hasNotificationListenerService |= actNotificationListenerService && + hasBindNotificationListenerServicePermission; + hasDreamService |= actDreamService && hasBindDreamServicePermission; hasOtherServices |= (!actImeService && !actWallpaperService && !actAccessibilityService && !actPrintService && - !actHostApduService && !actOffHostApduService); + !actHostApduService && !actOffHostApduService && + !actNotificationListenerService); + } else if (withinProvider) { + hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes; } } withinIntentFilter = false; @@ -858,18 +987,27 @@ int doDump(Bundle* bundle) continue; } depth++; - String8 tag(tree.getElementName(&len)); + + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n"); + goto bail; + } + String8 tag(ctag16); //printf("Depth %d, %s\n", depth, tag.string()); if (depth == 1) { if (tag != "manifest") { fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); goto bail; } - pkg = getAttribute(tree, NULL, "package", NULL); - printf("package: name='%s' ", pkg.string()); - int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error); + pkg = AaptXml::getAttribute(tree, NULL, "package", NULL); + printf("package: name='%s' ", + ResTable::normalizeForOutput(pkg.string()).string()); + int32_t versionCode = AaptXml::getIntegerAttribute(tree, VERSION_CODE_ATTR, + &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", + error.string()); goto bail; } if (versionCode > 0) { @@ -877,12 +1015,53 @@ int doDump(Bundle* bundle) } else { printf("versionCode='' "); } - String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error); + String8 versionName = AaptXml::getResolvedAttribute(res, tree, + VERSION_NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", + error.string()); goto bail; } - printf("versionName='%s'\n", versionName.string()); + printf("versionName='%s'", + ResTable::normalizeForOutput(versionName.string()).string()); + + String8 splitName = AaptXml::getAttribute(tree, NULL, "split"); + if (!splitName.isEmpty()) { + printf(" split='%s'", ResTable::normalizeForOutput( + splitName.string()).string()); + } + + String8 platformVersionName = AaptXml::getAttribute(tree, NULL, + "platformBuildVersionName"); + printf(" platformBuildVersionName='%s'", platformVersionName.string()); + printf("\n"); + + int32_t installLocation = AaptXml::getResolvedIntegerAttribute(res, tree, + INSTALL_LOCATION_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:installLocation' attribute: %s\n", + error.string()); + goto bail; + } + + if (installLocation >= 0) { + printf("install-location:'"); + switch (installLocation) { + case 0: + printf("auto"); + break; + case 1: + printf("internalOnly"); + break; + case 2: + printf("preferExternal"); + break; + default: + fprintf(stderr, "Invalid installLocation %d\n", installLocation); + goto bail; + } + printf("'\n"); + } } else if (depth == 2) { withinApplication = false; if (tag == "application") { @@ -893,17 +1072,19 @@ int doDump(Bundle* bundle) for (size_t i=0; i<NL; i++) { const char* localeStr = locales[i].string(); assets.setLocale(localeStr != NULL ? localeStr : ""); - String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error); + String8 llabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, + &error); if (llabel != "") { if (localeStr == NULL || strlen(localeStr) == 0) { label = llabel; - printf("application-label:'%s'\n", llabel.string()); + printf("application-label:'%s'\n", + ResTable::normalizeForOutput(llabel.string()).string()); } else { if (label == "") { label = llabel; } printf("application-label-%s:'%s'\n", localeStr, - llabel.string()); + ResTable::normalizeForOutput(llabel.string()).string()); } } } @@ -913,68 +1094,94 @@ int doDump(Bundle* bundle) for (size_t i=0; i<ND; i++) { tmpConfig.density = densities[i]; assets.setConfiguration(tmpConfig); - String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + String8 icon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, + &error); if (icon != "") { - printf("application-icon-%d:'%s'\n", densities[i], icon.string()); + printf("application-icon-%d:'%s'\n", densities[i], + ResTable::normalizeForOutput(icon.string()).string()); } } assets.setConfiguration(config); - String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + String8 icon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", + error.string()); goto bail; } - int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0); + int32_t testOnly = AaptXml::getIntegerAttribute(tree, TEST_ONLY_ATTR, 0, + &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", + error.string()); goto bail; } - printf("application: label='%s' ", label.string()); - printf("icon='%s'\n", icon.string()); + printf("application: label='%s' ", + ResTable::normalizeForOutput(label.string()).string()); + printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string()); if (testOnly != 0) { printf("testOnly='%d'\n", testOnly); } - int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0); + int32_t debuggable = AaptXml::getResolvedIntegerAttribute(res, tree, + DEBUGGABLE_ATTR, 0, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", + error.string()); goto bail; } if (debuggable != 0) { printf("application-debuggable\n"); } + + // We must search by name because the multiArch flag hasn't been API + // frozen yet. + int32_t multiArchIndex = tree.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, + "multiArch"); + if (multiArchIndex >= 0) { + Res_value value; + if (tree.getAttributeValue(multiArchIndex, &value) != NO_ERROR) { + if (value.dataType >= Res_value::TYPE_FIRST_INT && + value.dataType <= Res_value::TYPE_LAST_INT) { + hasMultiArch = value.data; + } + } + } } else if (tag == "uses-sdk") { - int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error); + int32_t code = AaptXml::getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error); if (error != "") { error = ""; - String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error); + String8 name = AaptXml::getResolvedAttribute(res, tree, + MIN_SDK_VERSION_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n", error.string()); goto bail; } if (name == "Donut") targetSdk = 4; - printf("sdkVersion:'%s'\n", name.string()); + printf("sdkVersion:'%s'\n", + ResTable::normalizeForOutput(name.string()).string()); } else if (code != -1) { targetSdk = code; printf("sdkVersion:'%d'\n", code); } - code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1); + code = AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR); if (code != -1) { printf("maxSdkVersion:'%d'\n", code); } - code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error); + code = AaptXml::getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error); if (error != "") { error = ""; - String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error); + String8 name = AaptXml::getResolvedAttribute(res, tree, + TARGET_SDK_VERSION_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n", error.string()); goto bail; } if (name == "Donut" && targetSdk < 4) targetSdk = 4; - printf("targetSdkVersion:'%s'\n", name.string()); + printf("targetSdkVersion:'%s'\n", + ResTable::normalizeForOutput(name.string()).string()); } else if (code != -1) { if (targetSdk < code) { targetSdk = code; @@ -982,16 +1189,16 @@ int doDump(Bundle* bundle) printf("targetSdkVersion:'%d'\n", code); } } else if (tag == "uses-configuration") { - int32_t reqTouchScreen = getIntegerAttribute(tree, - REQ_TOUCH_SCREEN_ATTR, NULL, 0); - int32_t reqKeyboardType = getIntegerAttribute(tree, - REQ_KEYBOARD_TYPE_ATTR, NULL, 0); - int32_t reqHardKeyboard = getIntegerAttribute(tree, - REQ_HARD_KEYBOARD_ATTR, NULL, 0); - int32_t reqNavigation = getIntegerAttribute(tree, - REQ_NAVIGATION_ATTR, NULL, 0); - int32_t reqFiveWayNav = getIntegerAttribute(tree, - REQ_FIVE_WAY_NAV_ATTR, NULL, 0); + int32_t reqTouchScreen = AaptXml::getIntegerAttribute(tree, + REQ_TOUCH_SCREEN_ATTR, 0); + int32_t reqKeyboardType = AaptXml::getIntegerAttribute(tree, + REQ_KEYBOARD_TYPE_ATTR, 0); + int32_t reqHardKeyboard = AaptXml::getIntegerAttribute(tree, + REQ_HARD_KEYBOARD_ATTR, 0); + int32_t reqNavigation = AaptXml::getIntegerAttribute(tree, + REQ_NAVIGATION_ATTR, 0); + int32_t reqFiveWayNav = AaptXml::getIntegerAttribute(tree, + REQ_FIVE_WAY_NAV_ATTR, 0); printf("uses-configuration:"); if (reqTouchScreen != 0) { printf(" reqTouchScreen='%d'", reqTouchScreen); @@ -1012,104 +1219,101 @@ int doDump(Bundle* bundle) } else if (tag == "supports-input") { withinSupportsInput = true; } else if (tag == "supports-screens") { - smallScreen = getIntegerAttribute(tree, - SMALL_SCREEN_ATTR, NULL, 1); - normalScreen = getIntegerAttribute(tree, - NORMAL_SCREEN_ATTR, NULL, 1); - largeScreen = getIntegerAttribute(tree, - LARGE_SCREEN_ATTR, NULL, 1); - xlargeScreen = getIntegerAttribute(tree, - XLARGE_SCREEN_ATTR, NULL, 1); - anyDensity = getIntegerAttribute(tree, - ANY_DENSITY_ATTR, NULL, 1); - requiresSmallestWidthDp = getIntegerAttribute(tree, - REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0); - compatibleWidthLimitDp = getIntegerAttribute(tree, - COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0); - largestWidthLimitDp = getIntegerAttribute(tree, - LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0); - } else if (tag == "uses-feature") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + smallScreen = AaptXml::getIntegerAttribute(tree, + SMALL_SCREEN_ATTR, 1); + normalScreen = AaptXml::getIntegerAttribute(tree, + NORMAL_SCREEN_ATTR, 1); + largeScreen = AaptXml::getIntegerAttribute(tree, + LARGE_SCREEN_ATTR, 1); + xlargeScreen = AaptXml::getIntegerAttribute(tree, + XLARGE_SCREEN_ATTR, 1); + anyDensity = AaptXml::getIntegerAttribute(tree, + ANY_DENSITY_ATTR, 1); + requiresSmallestWidthDp = AaptXml::getIntegerAttribute(tree, + REQUIRES_SMALLEST_WIDTH_DP_ATTR, 0); + compatibleWidthLimitDp = AaptXml::getIntegerAttribute(tree, + COMPATIBLE_WIDTH_LIMIT_DP_ATTR, 0); + largestWidthLimitDp = AaptXml::getIntegerAttribute(tree, + LARGEST_WIDTH_LIMIT_DP_ATTR, 0); + } else if (tag == "feature-group") { + withinFeatureGroup = true; + FeatureGroup group; + group.label = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:label' attribute:" + " %s\n", error.string()); + goto bail; + } + featureGroups.add(group); + } else if (tag == "uses-feature") { + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - int req = getIntegerAttribute(tree, - REQUIRED_ATTR, NULL, 1); - - if (name == "android.hardware.camera") { - specCameraFeature = true; - } else if (name == "android.hardware.camera.autofocus") { - // these have no corresponding permission to check for, - // but should imply the foundational camera permission - reqCameraAutofocusFeature = reqCameraAutofocusFeature || req; - specCameraAutofocusFeature = true; - } else if (req && (name == "android.hardware.camera.flash")) { - // these have no corresponding permission to check for, - // but should imply the foundational camera permission - reqCameraFlashFeature = true; - } else if (name == "android.hardware.location") { - specLocationFeature = true; - } else if (name == "android.hardware.location.network") { - specNetworkLocFeature = true; - reqNetworkLocFeature = reqNetworkLocFeature || req; - } else if (name == "android.hardware.location.gps") { - specGpsFeature = true; - reqGpsFeature = reqGpsFeature || req; - } else if (name == "android.hardware.bluetooth") { - specBluetoothFeature = true; - } else if (name == "android.hardware.touchscreen") { - specTouchscreenFeature = true; - } else if (name == "android.hardware.touchscreen.multitouch") { - specMultitouchFeature = true; - } else if (name == "android.hardware.touchscreen.multitouch.distinct") { - reqDistinctMultitouchFeature = reqDistinctMultitouchFeature || req; - } else if (name == "android.hardware.microphone") { - specMicrophoneFeature = true; - } else if (name == "android.hardware.wifi") { - specWiFiFeature = true; - } else if (name == "android.hardware.telephony") { - specTelephonyFeature = true; - } else if (req && (name == "android.hardware.telephony.gsm" || - name == "android.hardware.telephony.cdma")) { - // these have no corresponding permission to check for, - // but should imply the foundational telephony permission - reqTelephonySubFeature = true; - } else if (name == "android.hardware.screen.portrait") { - specScreenPortraitFeature = true; - } else if (name == "android.hardware.screen.landscape") { - specScreenLandscapeFeature = true; + int req = AaptXml::getIntegerAttribute(tree, + REQUIRED_ATTR, 1); + + commonFeatures.features.add(name, req); + if (req) { + addParentFeatures(&commonFeatures, name); } - printf("uses-feature%s:'%s'\n", - req ? "" : "-not-required", name.string()); } else { - int vers = getIntegerAttribute(tree, + int vers = AaptXml::getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error); if (error == "") { - printf("uses-gl-es:'0x%x'\n", vers); + if (vers > commonFeatures.openGLESVersion) { + commonFeatures.openGLESVersion = vers; + } } } } else if (tag == "uses-permission") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { if (name == "android.permission.CAMERA") { - hasCameraPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.camera", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.ACCESS_FINE_LOCATION") { - hasGpsPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.location.gps", + String8::format("requested %s permission", name.string()) + .string()); + addImpliedFeature(&impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.ACCESS_MOCK_LOCATION") { - hasMockLocPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.ACCESS_COARSE_LOCATION") { - hasCoarseLocPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.location.network", + String8::format("requested %s permission", name.string()) + .string()); + addImpliedFeature(&impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" || name == "android.permission.INSTALL_LOCATION_PROVIDER") { - hasGeneralLocPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.BLUETOOTH" || name == "android.permission.BLUETOOTH_ADMIN") { - hasBluetoothPermission = true; + if (targetSdk > 4) { + addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth", + String8::format("requested %s permission", name.string()) + .string()); + addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth", + "targetSdkVersion > 4"); + } } else if (name == "android.permission.RECORD_AUDIO") { - hasRecordAudioPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.microphone", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.ACCESS_WIFI_STATE" || name == "android.permission.CHANGE_WIFI_STATE" || name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") { - hasWiFiPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.wifi", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.CALL_PHONE" || name == "android.permission.CALL_PRIVILEGED" || name == "android.permission.MODIFY_PHONE_STATE" || @@ -1121,7 +1325,8 @@ int doDump(Bundle* bundle) name == "android.permission.SEND_SMS" || name == "android.permission.WRITE_APN_SETTINGS" || name == "android.permission.WRITE_SMS") { - hasTelephonyPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.telephony", + String8("requested a telephony permission").string()); } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") { hasWriteExternalStoragePermission = true; } else if (name == "android.permission.READ_EXTERNAL_STORAGE") { @@ -1137,53 +1342,61 @@ int doDump(Bundle* bundle) } else if (name == "android.permission.WRITE_CALL_LOG") { hasWriteCallLogPermission = true; } - printf("uses-permission:'%s'\n", name.string()); - int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1); - if (!req) { - printf("optional-permission:'%s'\n", name.string()); - } - } else { + + printUsesPermission(name, + AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, + AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); + } else { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } } else if (tag == "uses-package") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - printf("uses-package:'%s'\n", name.string()); + printf("uses-package:'%s'\n", + ResTable::normalizeForOutput(name.string()).string()); } else { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } } else if (tag == "original-package") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - printf("original-package:'%s'\n", name.string()); + printf("original-package:'%s'\n", + ResTable::normalizeForOutput(name.string()).string()); } else { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } } else if (tag == "supports-gl-texture") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - printf("supports-gl-texture:'%s'\n", name.string()); + printf("supports-gl-texture:'%s'\n", + ResTable::normalizeForOutput(name.string()).string()); } else { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } } else if (tag == "compatible-screens") { - printCompatibleScreens(tree); + printCompatibleScreens(tree, &error); + if (error != "") { + fprintf(stderr, "ERROR getting compatible screens: %s\n", + error.string()); + goto bail; + } depth--; } else if (tag == "package-verifier") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error); + String8 publicKey = AaptXml::getAttribute(tree, PUBLIC_KEY_ATTR, &error); if (publicKey != "" && error == "") { printf("package-verifier: name='%s' publicKey='%s'\n", - name.string(), publicKey.string()); + ResTable::normalizeForOutput(name.string()).string(), + ResTable::normalizeForOutput(publicKey.string()).string()); } } } @@ -1191,6 +1404,7 @@ int doDump(Bundle* bundle) withinActivity = false; withinReceiver = false; withinService = false; + withinProvider = false; hasIntentFilter = false; hasMetaHostPaymentCategory = false; hasMetaOffHostPaymentCategory = false; @@ -1199,56 +1413,72 @@ int doDump(Bundle* bundle) hasBindAccessibilityServicePermission = false; hasBindPrintServicePermission = false; hasBindNfcServicePermission = false; + hasRequiredSafAttributes = false; + hasBindNotificationListenerServicePermission = false; + hasBindDreamServicePermission = false; if (withinApplication) { if(tag == "activity") { withinActivity = true; - activityName = getAttribute(tree, NAME_ATTR, &error); + activityName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } - activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error); + activityLabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, + &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n", error.string()); goto bail; } - activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + activityIcon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, + &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string()); goto bail; } - int32_t orien = getResolvedIntegerAttribute(&res, tree, + activityBanner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR, + &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n", + error.string()); + goto bail; + } + + int32_t orien = AaptXml::getResolvedIntegerAttribute(res, tree, SCREEN_ORIENTATION_ATTR, &error); if (error == "") { if (orien == 0 || orien == 6 || orien == 8) { // Requests landscape, sensorLandscape, or reverseLandscape. - reqScreenLandscapeFeature = true; + addImpliedFeature(&impliedFeatures, "android.hardware.screen.landscape", + "one or more activities have specified a landscape orientation"); } else if (orien == 1 || orien == 7 || orien == 9) { // Requests portrait, sensorPortrait, or reversePortrait. - reqScreenPortraitFeature = true; + addImpliedFeature(&impliedFeatures, "android.hardware.screen.portrait", + "one or more activities have specified a portrait orientation"); } } } else if (tag == "uses-library") { - String8 libraryName = getAttribute(tree, NAME_ATTR, &error); + String8 libraryName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute for uses-library" " %s\n", error.string()); goto bail; } - int req = getIntegerAttribute(tree, - REQUIRED_ATTR, NULL, 1); + int req = AaptXml::getIntegerAttribute(tree, + REQUIRED_ATTR, 1); printf("uses-library%s:'%s'\n", - req ? "" : "-not-required", libraryName.string()); + req ? "" : "-not-required", ResTable::normalizeForOutput( + libraryName.string()).string()); } else if (tag == "receiver") { withinReceiver = true; - receiverName = getAttribute(tree, NAME_ATTR, &error); + receiverName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, @@ -1257,7 +1487,8 @@ int doDump(Bundle* bundle) goto bail; } - String8 permission = getAttribute(tree, PERMISSION_ATTR, &error); + String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR, + &error); if (error == "") { if (permission == "android.permission.BIND_DEVICE_ADMIN") { hasBindDeviceAdminPermission = true; @@ -1268,15 +1499,16 @@ int doDump(Bundle* bundle) } } else if (tag == "service") { withinService = true; - serviceName = getAttribute(tree, NAME_ATTR, &error); + serviceName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute for" - " service: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:name' attribute for " + "service:%s\n", error.string()); goto bail; } - String8 permission = getAttribute(tree, PERMISSION_ATTR, &error); + String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR, + &error); if (error == "") { if (permission == "android.permission.BIND_INPUT_METHOD") { hasBindInputMethodPermission = true; @@ -1286,20 +1518,95 @@ int doDump(Bundle* bundle) hasBindPrintServicePermission = true; } else if (permission == "android.permission.BIND_NFC_SERVICE") { hasBindNfcServicePermission = true; + } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") { + hasBindNotificationListenerServicePermission = true; + } else if (permission == "android.permission.BIND_DREAM_SERVICE") { + hasBindDreamServicePermission = true; } } else { fprintf(stderr, "ERROR getting 'android:permission' attribute for" " service '%s': %s\n", serviceName.string(), error.string()); } + } else if (tag == "provider") { + withinProvider = true; + + bool exported = AaptXml::getResolvedIntegerAttribute(res, tree, + EXPORTED_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:" + " %s\n", error.string()); + goto bail; + } + + bool grantUriPermissions = AaptXml::getResolvedIntegerAttribute( + res, tree, GRANT_URI_PERMISSIONS_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:" + " %s\n", error.string()); + goto bail; + } + + String8 permission = AaptXml::getResolvedAttribute(res, tree, + PERMISSION_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:" + " %s\n", error.string()); + goto bail; + } + + hasRequiredSafAttributes |= exported && grantUriPermissions && + permission == "android.permission.MANAGE_DOCUMENTS"; + + } else if (bundle->getIncludeMetaData() && tag == "meta-data") { + String8 metaDataName = AaptXml::getResolvedAttribute(res, tree, + NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute for " + "meta-data:%s\n", error.string()); + goto bail; + } + printf("meta-data: name='%s' ", + ResTable::normalizeForOutput(metaDataName.string()).string()); + printResolvedResourceAttribute(res, tree, VALUE_ATTR, String8("value"), + &error); + if (error != "") { + // Try looking for a RESOURCE_ATTR + error = ""; + printResolvedResourceAttribute(res, tree, RESOURCE_ATTR, + String8("resource"), &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:value' or " + "'android:resource' attribute for " + "meta-data:%s\n", error.string()); + goto bail; + } + } + printf("\n"); + } else if (withinSupportsInput && tag == "input-type") { + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); + if (name != "" && error == "") { + supportedInput.add(name); + } else { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } } - } else if (withinSupportsInput && tag == "input-type") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + } else if (withinFeatureGroup && tag == "uses-feature") { + FeatureGroup& top = featureGroups.editTop(); + + String8 name = AaptXml::getResolvedAttribute(res, tree, NAME_ATTR, &error); if (name != "" && error == "") { - supportedInput.add(name); + top.features.add(name, true); + addParentFeatures(&top, name); } else { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", - error.string()); - goto bail; + int vers = AaptXml::getIntegerAttribute(tree, GL_ES_VERSION_ATTR, + &error); + if (error == "") { + if (vers > top.openGLESVersion) { + top.openGLESVersion = vers; + } + } } } } else if (depth == 4) { @@ -1315,8 +1622,14 @@ int doDump(Bundle* bundle) actDeviceAdminEnabled = false; actHostApduService = false; actOffHostApduService = false; + actDocumentsProvider = false; + actNotificationListenerService = false; + actDreamService = false; + actCamera = false; + actCameraSecure = false; + catLauncher = false; } else if (withinService && tag == "meta-data") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute for" " meta-data tag in service '%s': %s\n", serviceName.string(), error.string()); @@ -1330,7 +1643,8 @@ int doDump(Bundle* bundle) offHost = false; } - String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error); + String8 xmlPath = AaptXml::getResolvedAttribute(res, tree, + RESOURCE_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:resource' attribute for" " meta-data tag in service '%s': %s\n", serviceName.string(), error.string()); @@ -1356,18 +1670,25 @@ int doDump(Bundle* bundle) } } } - } else if ((depth == 5) && withinIntentFilter){ + } else if ((depth == 5) && withinIntentFilter) { String8 action; if (tag == "action") { - action = getAttribute(tree, NAME_ATTR, &error); + action = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); goto bail; } + if (withinActivity) { if (action == "android.intent.action.MAIN") { isMainActivity = true; actMainActivity = true; + } else if (action == "android.media.action.STILL_IMAGE_CAMERA" || + action == "android.media.action.VIDEO_CAMERA") { + actCamera = true; + } else if (action == "android.media.action.STILL_IMAGE_CAMERA_SECURE") { + actCameraSecure = true; } } else if (withinReceiver) { if (action == "android.appwidget.action.APPWIDGET_UPDATE") { @@ -1388,6 +1709,14 @@ int doDump(Bundle* bundle) actHostApduService = true; } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") { actOffHostApduService = true; + } else if (action == "android.service.notification.NotificationListenerService") { + actNotificationListenerService = true; + } else if (action == "android.service.dreams.DreamService") { + actDreamService = true; + } + } else if (withinProvider) { + if (action == "android.content.action.DOCUMENTS_PROVIDER") { + actDocumentsProvider = true; } } if (action == "android.intent.action.SEARCH") { @@ -1396,14 +1725,19 @@ int doDump(Bundle* bundle) } if (tag == "category") { - String8 category = getAttribute(tree, NAME_ATTR, &error); + String8 category = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'name' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'name' attribute: %s\n", + error.string()); goto bail; } if (withinActivity) { if (category == "android.intent.category.LAUNCHER") { isLauncherActivity = true; + } else if (category == "android.intent.category.LEANBACK_LAUNCHER") { + isLeanbackLauncherActivity = true; + } else if (category == "android.intent.category.HOME") { + catLauncher = true; } } } @@ -1413,15 +1747,15 @@ int doDump(Bundle* bundle) // Pre-1.6 implicitly granted permission compatibility logic if (targetSdk < 4) { if (!hasWriteExternalStoragePermission) { - printf("uses-permission:'android.permission.WRITE_EXTERNAL_STORAGE'\n"); - printf("uses-implied-permission:'android.permission.WRITE_EXTERNAL_STORAGE'," \ - "'targetSdkVersion < 4'\n"); + printUsesPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE")); + printUsesImpliedPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"), + String8("targetSdkVersion < 4")); hasWriteExternalStoragePermission = true; } if (!hasReadPhoneStatePermission) { - printf("uses-permission:'android.permission.READ_PHONE_STATE'\n"); - printf("uses-implied-permission:'android.permission.READ_PHONE_STATE'," \ - "'targetSdkVersion < 4'\n"); + printUsesPermission(String8("android.permission.READ_PHONE_STATE")); + printUsesImpliedPermission(String8("android.permission.READ_PHONE_STATE"), + String8("targetSdkVersion < 4")); } } @@ -1430,188 +1764,109 @@ int doDump(Bundle* bundle) // do this (regardless of target API version) because we can't have // an app with write permission but not read permission. if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) { - printf("uses-permission:'android.permission.READ_EXTERNAL_STORAGE'\n"); - printf("uses-implied-permission:'android.permission.READ_EXTERNAL_STORAGE'," \ - "'requested WRITE_EXTERNAL_STORAGE'\n"); + printUsesPermission(String8("android.permission.READ_EXTERNAL_STORAGE")); + printUsesImpliedPermission(String8("android.permission.READ_EXTERNAL_STORAGE"), + String8("requested WRITE_EXTERNAL_STORAGE")); } // Pre-JellyBean call log permission compatibility. if (targetSdk < 16) { if (!hasReadCallLogPermission && hasReadContactsPermission) { - printf("uses-permission:'android.permission.READ_CALL_LOG'\n"); - printf("uses-implied-permission:'android.permission.READ_CALL_LOG'," \ - "'targetSdkVersion < 16 and requested READ_CONTACTS'\n"); + printUsesPermission(String8("android.permission.READ_CALL_LOG")); + printUsesImpliedPermission(String8("android.permission.READ_CALL_LOG"), + String8("targetSdkVersion < 16 and requested READ_CONTACTS")); } if (!hasWriteCallLogPermission && hasWriteContactsPermission) { - printf("uses-permission:'android.permission.WRITE_CALL_LOG'\n"); - printf("uses-implied-permission:'android.permission.WRITE_CALL_LOG'," \ - "'targetSdkVersion < 16 and requested WRITE_CONTACTS'\n"); + printUsesPermission(String8("android.permission.WRITE_CALL_LOG")); + printUsesImpliedPermission(String8("android.permission.WRITE_CALL_LOG"), + String8("targetSdkVersion < 16 and requested WRITE_CONTACTS")); } } - /* The following blocks handle printing "inferred" uses-features, based - * on whether related features or permissions are used by the app. - * Note that the various spec*Feature variables denote whether the - * relevant tag was *present* in the AndroidManfest, not that it was - * present and set to true. - */ - // Camera-related back-compatibility logic - if (!specCameraFeature) { - if (reqCameraFlashFeature) { - // if app requested a sub-feature (autofocus or flash) and didn't - // request the base camera feature, we infer that it meant to - printf("uses-feature:'android.hardware.camera'\n"); - printf("uses-implied-feature:'android.hardware.camera'," \ - "'requested android.hardware.camera.flash feature'\n"); - } else if (reqCameraAutofocusFeature) { - // if app requested a sub-feature (autofocus or flash) and didn't - // request the base camera feature, we infer that it meant to - printf("uses-feature:'android.hardware.camera'\n"); - printf("uses-implied-feature:'android.hardware.camera'," \ - "'requested android.hardware.camera.autofocus feature'\n"); - } else if (hasCameraPermission) { - // if app wants to use camera but didn't request the feature, we infer - // that it meant to, and further that it wants autofocus - // (which was the 1.0 - 1.5 behavior) - printf("uses-feature:'android.hardware.camera'\n"); - if (!specCameraAutofocusFeature) { - printf("uses-feature:'android.hardware.camera.autofocus'\n"); - printf("uses-implied-feature:'android.hardware.camera.autofocus'," \ - "'requested android.permission.CAMERA permission'\n"); - } - } - } + addImpliedFeature(&impliedFeatures, "android.hardware.touchscreen", + "default feature for all apps"); - // Location-related back-compatibility logic - if (!specLocationFeature && - (hasMockLocPermission || hasCoarseLocPermission || hasGpsPermission || - hasGeneralLocPermission || reqNetworkLocFeature || reqGpsFeature)) { - // if app either takes a location-related permission or requests one of the - // sub-features, we infer that it also meant to request the base location feature - printf("uses-feature:'android.hardware.location'\n"); - printf("uses-implied-feature:'android.hardware.location'," \ - "'requested a location access permission'\n"); - } - if (!specGpsFeature && hasGpsPermission) { - // if app takes GPS (FINE location) perm but does not request the GPS - // feature, we infer that it meant to - printf("uses-feature:'android.hardware.location.gps'\n"); - printf("uses-implied-feature:'android.hardware.location.gps'," \ - "'requested android.permission.ACCESS_FINE_LOCATION permission'\n"); - } - if (!specNetworkLocFeature && hasCoarseLocPermission) { - // if app takes Network location (COARSE location) perm but does not request the - // network location feature, we infer that it meant to - printf("uses-feature:'android.hardware.location.network'\n"); - printf("uses-implied-feature:'android.hardware.location.network'," \ - "'requested android.permission.ACCESS_COARSE_LOCATION permission'\n"); - } - - // Bluetooth-related compatibility logic - if (!specBluetoothFeature && hasBluetoothPermission && (targetSdk > 4)) { - // if app takes a Bluetooth permission but does not request the Bluetooth - // feature, we infer that it meant to - printf("uses-feature:'android.hardware.bluetooth'\n"); - printf("uses-implied-feature:'android.hardware.bluetooth'," \ - "'requested android.permission.BLUETOOTH or android.permission.BLUETOOTH_ADMIN " \ - "permission and targetSdkVersion > 4'\n"); - } - - // Microphone-related compatibility logic - if (!specMicrophoneFeature && hasRecordAudioPermission) { - // if app takes the record-audio permission but does not request the microphone - // feature, we infer that it meant to - printf("uses-feature:'android.hardware.microphone'\n"); - printf("uses-implied-feature:'android.hardware.microphone'," \ - "'requested android.permission.RECORD_AUDIO permission'\n"); - } + const size_t numFeatureGroups = featureGroups.size(); + if (numFeatureGroups == 0) { + // If no <feature-group> tags were defined, apply auto-implied features. + printFeatureGroup(commonFeatures, &impliedFeatures); - // WiFi-related compatibility logic - if (!specWiFiFeature && hasWiFiPermission) { - // if app takes one of the WiFi permissions but does not request the WiFi - // feature, we infer that it meant to - printf("uses-feature:'android.hardware.wifi'\n"); - printf("uses-implied-feature:'android.hardware.wifi'," \ - "'requested android.permission.ACCESS_WIFI_STATE, " \ - "android.permission.CHANGE_WIFI_STATE, or " \ - "android.permission.CHANGE_WIFI_MULTICAST_STATE permission'\n"); - } + } else { + // <feature-group> tags are defined, so we ignore implied features and + for (size_t i = 0; i < numFeatureGroups; i++) { + FeatureGroup& grp = featureGroups.editItemAt(i); - // Telephony-related compatibility logic - if (!specTelephonyFeature && (hasTelephonyPermission || reqTelephonySubFeature)) { - // if app takes one of the telephony permissions or requests a sub-feature but - // does not request the base telephony feature, we infer that it meant to - printf("uses-feature:'android.hardware.telephony'\n"); - printf("uses-implied-feature:'android.hardware.telephony'," \ - "'requested a telephony-related permission or feature'\n"); - } + if (commonFeatures.openGLESVersion > grp.openGLESVersion) { + grp.openGLESVersion = commonFeatures.openGLESVersion; + } - // Touchscreen-related back-compatibility logic - if (!specTouchscreenFeature) { // not a typo! - // all apps are presumed to require a touchscreen, unless they explicitly say - // <uses-feature android:name="android.hardware.touchscreen" android:required="false"/> - // Note that specTouchscreenFeature is true if the tag is present, regardless - // of whether its value is true or false, so this is safe - printf("uses-feature:'android.hardware.touchscreen'\n"); - printf("uses-implied-feature:'android.hardware.touchscreen'," \ - "'assumed you require a touch screen unless explicitly made optional'\n"); - } - if (!specMultitouchFeature && reqDistinctMultitouchFeature) { - // if app takes one of the telephony permissions or requests a sub-feature but - // does not request the base telephony feature, we infer that it meant to - printf("uses-feature:'android.hardware.touchscreen.multitouch'\n"); - printf("uses-implied-feature:'android.hardware.touchscreen.multitouch'," \ - "'requested android.hardware.touchscreen.multitouch.distinct feature'\n"); - } + // Merge the features defined in the top level (not inside a <feature-group>) + // with this feature group. + const size_t numCommonFeatures = commonFeatures.features.size(); + for (size_t j = 0; j < numCommonFeatures; j++) { + if (grp.features.indexOfKey(commonFeatures.features.keyAt(j)) < 0) { + grp.features.add(commonFeatures.features.keyAt(j), + commonFeatures.features[j]); + } + } - // Landscape/portrait-related compatibility logic - if (!specScreenLandscapeFeature && !specScreenPortraitFeature) { - // If the app has specified any activities in its manifest - // that request a specific orientation, then assume that - // orientation is required. - if (reqScreenLandscapeFeature) { - printf("uses-feature:'android.hardware.screen.landscape'\n"); - printf("uses-implied-feature:'android.hardware.screen.landscape'," \ - "'one or more activities have specified a landscape orientation'\n"); - } - if (reqScreenPortraitFeature) { - printf("uses-feature:'android.hardware.screen.portrait'\n"); - printf("uses-implied-feature:'android.hardware.screen.portrait'," \ - "'one or more activities have specified a portrait orientation'\n"); + if (!grp.features.isEmpty()) { + printFeatureGroup(grp); + } } } - if (hasMainActivity) { - printf("main\n"); - } + if (hasWidgetReceivers) { - printf("app-widget\n"); + printComponentPresence("app-widget"); } if (hasDeviceAdminReceiver) { - printf("device-admin\n"); + printComponentPresence("device-admin"); } if (hasImeService) { - printf("ime\n"); + printComponentPresence("ime"); } if (hasWallpaperService) { - printf("wallpaper\n"); + printComponentPresence("wallpaper"); } if (hasAccessibilityService) { - printf("accessibility\n"); + printComponentPresence("accessibility"); } if (hasPrintService) { - printf("print\n"); + printComponentPresence("print-service"); } if (hasPaymentService) { - printf("payment\n"); + printComponentPresence("payment"); + } + if (isSearchable) { + printComponentPresence("search"); + } + if (hasDocumentsProvider) { + printComponentPresence("document-provider"); + } + if (hasLauncher) { + printComponentPresence("launcher"); + } + if (hasNotificationListenerService) { + printComponentPresence("notification-listener"); + } + if (hasDreamService) { + printComponentPresence("dream"); + } + if (hasCameraActivity) { + printComponentPresence("camera"); + } + if (hasCameraSecureActivity) { + printComponentPresence("camera-secure"); + } + + if (hasMainActivity) { + printf("main\n"); } if (hasOtherActivities) { printf("other-activities\n"); } - if (isSearchable) { - printf("search\n"); - } - if (hasOtherReceivers) { + if (hasOtherReceivers) { printf("other-receivers\n"); } if (hasOtherServices) { @@ -1623,7 +1878,9 @@ int doDump(Bundle* bundle) if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0 && requiresSmallestWidthDp > 0) { int compatWidth = compatibleWidthLimitDp; - if (compatWidth <= 0) compatWidth = requiresSmallestWidthDp; + if (compatWidth <= 0) { + compatWidth = requiresSmallestWidthDp; + } if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) { smallScreen = -1; } else { @@ -1668,10 +1925,18 @@ int doDump(Bundle* bundle) || compatibleWidthLimitDp > 0) ? -1 : 0; } printf("supports-screens:"); - if (smallScreen != 0) printf(" 'small'"); - if (normalScreen != 0) printf(" 'normal'"); - if (largeScreen != 0) printf(" 'large'"); - if (xlargeScreen != 0) printf(" 'xlarge'"); + if (smallScreen != 0) { + printf(" 'small'"); + } + if (normalScreen != 0) { + printf(" 'normal'"); + } + if (largeScreen != 0) { + printf(" 'large'"); + } + if (xlargeScreen != 0) { + printf(" 'xlarge'"); + } printf("\n"); printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false"); if (requiresSmallestWidthDp > 0) { @@ -1705,11 +1970,54 @@ int doDump(Bundle* bundle) AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib"); if (dir != NULL) { if (dir->getFileCount() > 0) { - printf("native-code:"); + SortedVector<String8> architectures; for (size_t i=0; i<dir->getFileCount(); i++) { - printf(" '%s'", dir->getFileName(i).string()); + architectures.add(ResTable::normalizeForOutput( + dir->getFileName(i).string())); + } + + bool outputAltNativeCode = false; + // A multiArch package is one that contains 64-bit and + // 32-bit versions of native code and expects 3rd-party + // apps to load these native code libraries. Since most + // 64-bit systems also support 32-bit apps, the apps + // loading this multiArch package's code may be either + // 32-bit or 64-bit. + if (hasMultiArch) { + // If this is a multiArch package, report the 64-bit + // version only. Then as a separate entry, report the + // rest. + // + // If we report the 32-bit architecture, this APK will + // be installed on a 32-bit device, causing a large waste + // of bandwidth and disk space. This assumes that + // the developer of the multiArch package has also + // made a version that is 32-bit only. + String8 intel64("x86_64"); + String8 arm64("arm64-v8a"); + ssize_t index = architectures.indexOf(intel64); + if (index < 0) { + index = architectures.indexOf(arm64); + } + + if (index >= 0) { + printf("native-code: '%s'\n", architectures[index].string()); + architectures.removeAt(index); + outputAltNativeCode = true; + } + } + + const size_t archCount = architectures.size(); + if (archCount > 0) { + if (outputAltNativeCode) { + printf("alt-"); + } + printf("native-code:"); + for (size_t i = 0; i < archCount; i++) { + printf(" '%s'", architectures[i].string()); + } + printf("\n"); } - printf("\n"); } delete dir; } @@ -1780,7 +2088,8 @@ int doAdd(Bundle* bundle) } else { if (bundle->getJunkPath()) { String8 storageName = String8(fileName).getPathLeaf(); - printf(" '%s' as '%s'...\n", fileName, storageName.string()); + printf(" '%s' as '%s'...\n", fileName, + ResTable::normalizeForOutput(storageName.string()).string()); result = zip->add(fileName, storageName.string(), bundle->getCompressionMethod(), NULL); } else { @@ -1790,12 +2099,13 @@ int doAdd(Bundle* bundle) } if (result != NO_ERROR) { fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName); - if (result == NAME_NOT_FOUND) + if (result == NAME_NOT_FOUND) { fprintf(stderr, ": file not found\n"); - else if (result == ALREADY_EXISTS) + } else if (result == ALREADY_EXISTS) { fprintf(stderr, ": already exists in archive\n"); - else + } else { fprintf(stderr, "\n"); + } goto bail; } } @@ -1862,6 +2172,58 @@ bail: return (result != NO_ERROR); } +static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder, bool ignoreConfig=false) { + const size_t numDirs = dir->getDirs().size(); + for (size_t i = 0; i < numDirs; i++) { + bool ignore = ignoreConfig; + const sp<AaptDir>& subDir = dir->getDirs().valueAt(i); + const char* dirStr = subDir->getLeaf().string(); + if (!ignore && strstr(dirStr, "mipmap") == dirStr) { + ignore = true; + } + status_t err = addResourcesToBuilder(subDir, builder, ignore); + if (err != NO_ERROR) { + return err; + } + } + + const size_t numFiles = dir->getFiles().size(); + for (size_t i = 0; i < numFiles; i++) { + sp<AaptGroup> gp = dir->getFiles().valueAt(i); + const size_t numConfigs = gp->getFiles().size(); + for (size_t j = 0; j < numConfigs; j++) { + status_t err = NO_ERROR; + if (ignoreConfig) { + err = builder->getBaseSplit()->addEntry(gp->getPath(), gp->getFiles().valueAt(j)); + } else { + err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j)); + } + if (err != NO_ERROR) { + fprintf(stderr, "Failed to add %s (%s) to builder.\n", + gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string()); + return err; + } + } + } + return NO_ERROR; +} + +static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) { + if (split->isBase()) { + return original; + } + + String8 ext(original.getPathExtension()); + if (ext == String8(".apk")) { + return String8::format("%s_%s%s", + original.getBasePath().string(), + split->getDirectorySafeName().string(), + ext.string()); + } + + return String8::format("%s_%s", original.string(), + split->getDirectorySafeName().string()); +} /* * Package up an asset directory and associated application files. @@ -1875,23 +2237,24 @@ int doPackage(Bundle* bundle) int N; FILE* fp; String8 dependencyFile; + sp<ApkBuilder> builder; // -c en_XA or/and ar_XB means do pseudolocalization - ResourceFilter filter; - err = filter.parse(bundle->getConfigurations()); + sp<WeakResourceFilter> configFilter = new WeakResourceFilter(); + err = configFilter->parse(bundle->getConfigurations()); if (err != NO_ERROR) { goto bail; } - if (filter.containsPseudo()) { + if (configFilter->containsPseudo()) { bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED); } - if (filter.containsPseudoBidi()) { + if (configFilter->containsPseudoBidi()) { bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI); } N = bundle->getFileSpecCount(); if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0 - && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDir() == NULL) { + && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) { fprintf(stderr, "ERROR: no input files\n"); goto bail; } @@ -1933,9 +2296,32 @@ int doPackage(Bundle* bundle) assets->print(String8()); } + // Create the ApkBuilder, which will collect the compiled files + // to write to the final APK (or sets of APKs if we are building + // a Split APK. + builder = new ApkBuilder(configFilter); + + // If we are generating a Split APK, find out which configurations to split on. + if (bundle->getSplitConfigurations().size() > 0) { + const Vector<String8>& splitStrs = bundle->getSplitConfigurations(); + const size_t numSplits = splitStrs.size(); + for (size_t i = 0; i < numSplits; i++) { + std::set<ConfigDescription> configs; + if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) { + fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string()); + goto bail; + } + + err = builder->createSplitForConfigs(configs); + if (err != NO_ERROR) { + goto bail; + } + } + } + // If they asked for any fileAs that need to be compiled, do so. if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) { - err = buildResources(bundle, assets); + err = buildResources(bundle, assets, builder); if (err != 0) { goto bail; } @@ -1978,10 +2364,12 @@ int doPackage(Bundle* bundle) if (bundle->getCustomPackage() == NULL) { // Write the R.java file into the appropriate class directory // e.g. gen/com/foo/app/R.java - err = writeResourceSymbols(bundle, assets, assets->getPackage(), true); + err = writeResourceSymbols(bundle, assets, assets->getPackage(), true, + bundle->getBuildSharedLibrary()); } else { const String8 customPkg(bundle->getCustomPackage()); - err = writeResourceSymbols(bundle, assets, customPkg, true); + err = writeResourceSymbols(bundle, assets, customPkg, true, + bundle->getBuildSharedLibrary()); } if (err < 0) { goto bail; @@ -1995,7 +2383,7 @@ int doPackage(Bundle* bundle) char* packageString = strtok(libs.lockBuffer(libs.length()), ":"); while (packageString != NULL) { // Write the R.java file out with the correct package name - err = writeResourceSymbols(bundle, assets, String8(packageString), true); + err = writeResourceSymbols(bundle, assets, String8(packageString), true, false); if (err < 0) { goto bail; } @@ -2004,11 +2392,11 @@ int doPackage(Bundle* bundle) libs.unlockBuffer(); } } else { - err = writeResourceSymbols(bundle, assets, assets->getPackage(), false); + err = writeResourceSymbols(bundle, assets, assets->getPackage(), false, false); if (err < 0) { goto bail; } - err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true); + err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true, false); if (err < 0) { goto bail; } @@ -2022,11 +2410,24 @@ int doPackage(Bundle* bundle) // Write the apk if (outputAPKFile) { - err = writeAPK(bundle, assets, String8(outputAPKFile)); + // Gather all resources and add them to the APK Builder. The builder will then + // figure out which Split they belong in. + err = addResourcesToBuilder(assets, builder); if (err != NO_ERROR) { - fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile); goto bail; } + + const Vector<sp<ApkSplit> >& splits = builder->getSplits(); + const size_t numSplits = splits.size(); + for (size_t i = 0; i < numSplits; i++) { + const sp<ApkSplit>& split = splits[i]; + String8 outputPath = buildApkName(String8(outputAPKFile), split); + err = writeAPK(bundle, outputPath, split); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string()); + goto bail; + } + } } // If we've been asked to generate a dependency file, we need to finish up here. @@ -2063,7 +2464,7 @@ bail: * * POSTCONDITIONS * Destination directory will be updated to match the PNG files in - * the source directory. + * the source directory. */ int doCrunch(Bundle* bundle) { |