+
+
+class ProguardKeepSet
+{
+public:
+ // { rule --> { file locations } }
+ KeyedVector<String8, SortedVector<String8> > rules;
+
+ void add(const String8& rule, const String8& where);
+};
+
+void ProguardKeepSet::add(const String8& rule, const String8& where)
+{
+ ssize_t index = rules.indexOfKey(rule);
+ if (index < 0) {
+ index = rules.add(rule, SortedVector<String8>());
+ }
+ rules.editValueAt(index).add(where);
+}
+
+void
+addProguardKeepRule(ProguardKeepSet* keep, const String8& inClassName,
+ const char* pkg, const String8& srcName, int line)
+{
+ String8 className(inClassName);
+ if (pkg != NULL) {
+ // asdf --> package.asdf
+ // .asdf .a.b --> package.asdf package.a.b
+ // asdf.adsf --> asdf.asdf
+ const char* p = className.string();
+ const char* q = strchr(p, '.');
+ if (p == q) {
+ className = pkg;
+ className.append(inClassName);
+ } else if (q == NULL) {
+ className = pkg;
+ className.append(".");
+ className.append(inClassName);
+ }
+ }
+
+ String8 rule("-keep class ");
+ rule += className;
+ rule += " { <init>(...); }";
+
+ String8 location("view ");
+ location += srcName;
+ char lineno[20];
+ sprintf(lineno, ":%d", line);
+ location += lineno;
+
+ keep->add(rule, location);
+}
+
+void
+addProguardKeepMethodRule(ProguardKeepSet* keep, const String8& memberName,
+ const char* pkg, const String8& srcName, int line)
+{
+ String8 rule("-keepclassmembers class * { *** ");
+ rule += memberName;
+ rule += "(...); }";
+
+ String8 location("onClick ");
+ location += srcName;
+ char lineno[20];
+ sprintf(lineno, ":%d", line);
+ location += lineno;
+
+ keep->add(rule, location);
+}
+
+status_t
+writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
+{
+ status_t err;
+ ResXMLTree tree;
+ size_t len;
+ ResXMLTree::event_code_t code;
+ int depth = 0;
+ bool inApplication = false;
+ String8 error;
+ sp<AaptGroup> assGroup;
+ sp<AaptFile> assFile;
+ String8 pkg;
+
+ // First, look for a package file to parse. This is required to
+ // be able to generate the resource information.
+ assGroup = assets->getFiles().valueFor(String8("AndroidManifest.xml"));
+ if (assGroup == NULL) {
+ fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
+ return -1;
+ }
+
+ if (assGroup->getFiles().size() != 1) {
+ fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
+ assGroup->getFiles().valueAt(0)->getPrintableSource().string());
+ }
+
+ assFile = assGroup->getFiles().valueAt(0);
+
+ err = parseXMLResource(assFile, &tree);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ tree.restart();
+
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (/* name == "Application" && */ depth == 2) {
+ inApplication = false;
+ }
+ depth--;
+ continue;
+ }
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ depth++;
+ String8 tag(tree.getElementName(&len));
+ // printf("Depth %d tag %s\n", depth, tag.string());
+ bool keepTag = false;
+ if (depth == 1) {
+ if (tag != "manifest") {
+ fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+ return -1;
+ }
+ pkg = getAttribute(tree, NULL, "package", NULL);
+ } else if (depth == 2) {
+ if (tag == "application") {
+ inApplication = true;
+ keepTag = true;
+
+ String8 agent = getAttribute(tree, "http://schemas.android.com/apk/res/android",
+ "backupAgent", &error);
+ if (agent.length() > 0) {
+ addProguardKeepRule(keep, agent, pkg.string(),
+ assFile->getPrintableSource(), tree.getLineNumber());
+ }
+ } else if (tag == "instrumentation") {
+ keepTag = true;
+ }
+ }
+ if (!keepTag && inApplication && depth == 3) {
+ if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") {
+ keepTag = true;
+ }
+ }
+ if (keepTag) {
+ String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android",
+ "name", &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ return -1;
+ }
+ if (name.length() > 0) {
+ addProguardKeepRule(keep, name, pkg.string(),
+ assFile->getPrintableSource(), tree.getLineNumber());
+ }
+ }
+ }
+
+ return NO_ERROR;
+}
+
+struct NamespaceAttributePair {
+ const char* ns;
+ const char* attr;
+
+ NamespaceAttributePair(const char* n, const char* a) : ns(n), attr(a) {}
+ NamespaceAttributePair() : ns(NULL), attr(NULL) {}
+};
+
+status_t
+writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile,
+ const char* startTag, const KeyedVector<String8, NamespaceAttributePair>* tagAttrPairs)
+{
+ status_t err;
+ ResXMLTree tree;
+ size_t len;
+ ResXMLTree::event_code_t code;
+
+ err = parseXMLResource(layoutFile, &tree);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ tree.restart();
+
+ if (startTag != NULL) {
+ bool haveStart = false;
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ String8 tag(tree.getElementName(&len));
+ if (tag == startTag) {
+ haveStart = true;
+ }
+ break;
+ }
+ if (!haveStart) {
+ return NO_ERROR;
+ }
+ }
+
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ String8 tag(tree.getElementName(&len));
+
+ // If there is no '.', we'll assume that it's one of the built in names.
+ if (strchr(tag.string(), '.')) {
+ addProguardKeepRule(keep, tag, NULL,
+ layoutFile->getPrintableSource(), tree.getLineNumber());
+ } else if (tagAttrPairs != NULL) {
+ ssize_t tagIndex = tagAttrPairs->indexOfKey(tag);
+ if (tagIndex >= 0) {
+ const NamespaceAttributePair& nsAttr = tagAttrPairs->valueAt(tagIndex);
+ ssize_t attrIndex = tree.indexOfAttribute(nsAttr.ns, nsAttr.attr);
+ if (attrIndex < 0) {
+ // fprintf(stderr, "%s:%d: <%s> does not have attribute %s:%s.\n",
+ // layoutFile->getPrintableSource().string(), tree.getLineNumber(),
+ // tag.string(), nsAttr.ns, nsAttr.attr);
+ } else {
+ size_t len;
+ addProguardKeepRule(keep,
+ String8(tree.getAttributeStringValue(attrIndex, &len)), NULL,
+ layoutFile->getPrintableSource(), tree.getLineNumber());
+ }
+ }
+ }
+ ssize_t attrIndex = tree.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "onClick");
+ if (attrIndex >= 0) {
+ size_t len;
+ addProguardKeepMethodRule(keep,
+ String8(tree.getAttributeStringValue(attrIndex, &len)), NULL,
+ layoutFile->getPrintableSource(), tree.getLineNumber());
+ }
+ }
+
+ return NO_ERROR;
+}
+
+static void addTagAttrPair(KeyedVector<String8, NamespaceAttributePair>* dest,
+ const char* tag, const char* ns, const char* attr) {
+ dest->add(String8(tag), NamespaceAttributePair(ns, attr));
+}
+
+status_t
+writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
+{
+ status_t err;
+
+ // tag:attribute pairs that should be checked in layout files.
+ KeyedVector<String8, NamespaceAttributePair> kLayoutTagAttrPairs;
+ addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, "class");
+ addTagAttrPair(&kLayoutTagAttrPairs, "fragment", NULL, "class");
+ addTagAttrPair(&kLayoutTagAttrPairs, "fragment", RESOURCES_ANDROID_NAMESPACE, "name");
+
+ // tag:attribute pairs that should be checked in xml files.
+ KeyedVector<String8, NamespaceAttributePair> kXmlTagAttrPairs;
+ addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment");
+ addTagAttrPair(&kXmlTagAttrPairs, "header", RESOURCES_ANDROID_NAMESPACE, "fragment");
+
+ const Vector<sp<AaptDir> >& dirs = assets->resDirs();
+ const size_t K = dirs.size();
+ for (size_t k=0; k<K; k++) {
+ const sp<AaptDir>& d = dirs.itemAt(k);
+ const String8& dirName = d->getLeaf();
+ const char* startTag = NULL;
+ const KeyedVector<String8, NamespaceAttributePair>* tagAttrPairs = NULL;
+ if ((dirName == String8("layout")) || (strncmp(dirName.string(), "layout-", 7) == 0)) {
+ tagAttrPairs = &kLayoutTagAttrPairs;
+ } else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) {
+ startTag = "PreferenceScreen";
+ tagAttrPairs = &kXmlTagAttrPairs;
+ } else if ((dirName == String8("menu")) || (strncmp(dirName.string(), "menu-", 5) == 0)) {
+ startTag = "menu";
+ tagAttrPairs = NULL;
+ } else {
+ continue;
+ }
+
+ const KeyedVector<String8,sp<AaptGroup> > groups = d->getFiles();
+ const size_t N = groups.size();
+ for (size_t i=0; i<N; i++) {
+ const sp<AaptGroup>& group = groups.valueAt(i);
+ const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles();
+ const size_t M = files.size();
+ for (size_t j=0; j<M; j++) {
+ err = writeProguardForXml(keep, files.valueAt(j), startTag, tagAttrPairs);
+ if (err < 0) {
+ return err;
+ }
+ }
+ }
+ }
+ // Handle the overlays
+ sp<AaptAssets> overlay = assets->getOverlay();
+ if (overlay.get()) {
+ return writeProguardForLayouts(keep, overlay);
+ }
+ return NO_ERROR;
+}
+
+status_t
+writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets)
+{
+ status_t err = -1;
+
+ if (!bundle->getProguardFile()) {
+ return NO_ERROR;
+ }
+
+ ProguardKeepSet keep;
+
+ err = writeProguardForAndroidManifest(&keep, assets);
+ if (err < 0) {
+ return err;
+ }
+
+ err = writeProguardForLayouts(&keep, assets);
+ if (err < 0) {
+ return err;
+ }
+
+ FILE* fp = fopen(bundle->getProguardFile(), "w+");
+ if (fp == NULL) {
+ fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
+ bundle->getProguardFile(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+
+ const KeyedVector<String8, SortedVector<String8> >& rules = keep.rules;
+ const size_t N = rules.size();
+ for (size_t i=0; i<N; i++) {
+ const SortedVector<String8>& locations = rules.valueAt(i);
+ const size_t M = locations.size();
+ for (size_t j=0; j<M; j++) {
+ fprintf(fp, "# %s\n", locations.itemAt(j).string());
+ }
+ fprintf(fp, "%s\n\n", rules.keyAt(i).string());
+ }
+ fclose(fp);
+
+ return err;
+}
+
+// Loops through the string paths and writes them to the file pointer
+// Each file path is written on its own line with a terminating backslash.
+status_t writePathsToFile(const sp<FilePathStore>& files, FILE* fp)
+{
+ status_t deps = -1;
+ for (size_t file_i = 0; file_i < files->size(); ++file_i) {
+ // Add the full file path to the dependency file
+ fprintf(fp, "%s \\\n", files->itemAt(file_i).string());
+ deps++;
+ }
+ return deps;
+}
+
+status_t
+writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets, FILE* fp, bool includeRaw)
+{
+ status_t deps = -1;
+ deps += writePathsToFile(assets->getFullResPaths(), fp);
+ if (includeRaw) {
+ deps += writePathsToFile(assets->getFullAssetPaths(), fp);
+ }
+ return deps;
+}