/* * Copyright (C) 2012 Sony Mobile Communications AB * * This file is part of ApkAnalyser. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package andreflect.xml; import gui.actions.AbstractCanceableAction; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import analyser.gui.LineBuilder; import analyser.gui.LineBuilderFormatter; import analyser.gui.MainFrame; import analyser.gui.actions.ShowBytecodeAction; import analyser.logic.RefClass; import analyser.logic.RefContext; import analyser.logic.RefPackage; import analyser.logic.Reference; import andreflect.ApkClassContext; import andreflect.DexClass; import andreflect.DexField; import andreflect.DexReferenceCache; import andreflect.DexReferenceCache.LoadConstRes; import andreflect.DexReferenceCache.LoadConstString; import andreflect.DexResSpec; import andreflect.gui.action.XmlFindLabelAction; import andreflect.gui.action.XmlViewReferenceAction; import andreflect.gui.action.XmlViewerAction; import andreflect.xml.XmlParser.XmlLine; import brut.androlib.AndrolibException; import brut.androlib.res.data.ResConfig; import brut.androlib.res.data.ResConfigFlags; import brut.androlib.res.data.ResPackage; import brut.androlib.res.data.ResResSpec; import brut.androlib.res.data.ResResource; import brut.androlib.res.data.ResTable; import brut.androlib.res.data.value.ResArrayValue; import brut.androlib.res.data.value.ResAttr; import brut.androlib.res.data.value.ResBagValue; import brut.androlib.res.data.value.ResBoolValue; import brut.androlib.res.data.value.ResColorValue; import brut.androlib.res.data.value.ResDimenValue; import brut.androlib.res.data.value.ResEnumAttr; import brut.androlib.res.data.value.ResFileValue; import brut.androlib.res.data.value.ResFlagsAttr; import brut.androlib.res.data.value.ResFlagsAttr.FlagItem; import brut.androlib.res.data.value.ResFloatValue; import brut.androlib.res.data.value.ResFractionValue; import brut.androlib.res.data.value.ResIntValue; import brut.androlib.res.data.value.ResPluralsValue; import brut.androlib.res.data.value.ResReferenceValue; import brut.androlib.res.data.value.ResScalarValue; import brut.androlib.res.data.value.ResStringValue; import brut.androlib.res.data.value.ResStyleValue; import brut.androlib.res.data.value.ResValue; import brut.util.Duo; public class XmlResourceChecker { public static final int COLOR_SYMBOL = 0x00B00000; public static final int COLOR_KEYWORD = 0x880088; public static final int COLOR_TEXT = 0x000000; public static final int COLOR_HEX = 0x008800; public static final int COLOR_LABEL = 0x000088; public static final int COLOR_COMMENT = 0x888800; public static final int COLOR_ERROR = 0xff0000; private ResTable mResTable = null; private HashSet<String> mLanguageSet = null; private final XmlParser mParser; public XmlResourceChecker(XmlParser parser) { mResTable = parser.getResTable(); mParser = parser; } public void showFindLabel(String label, ApkClassContext ctx, MainFrame mainFrame, XmlFindLabelAction action) { Pattern pattern = Pattern.compile(label, Pattern.CASE_INSENSITIVE); HashSet<ResResSpec> resSpecs = new HashSet<ResResSpec>(); for (ResPackage pkg : mResTable.listMainPackages()) { for (ResResSpec spec : pkg.listResSpecs()) { for (ResResource res : spec.listResources()) { if (res.getValue() instanceof ResStringValue) { try { String value = ((ResScalarValue) res.getValue()).encodeAsResXmlValue(); Matcher matcher = pattern.matcher(value); if (matcher.find()) { resSpecs.add(spec); } } catch (Exception e) { e.printStackTrace(); } } } } } ArrayList<LoadConstString> codeString = new ArrayList<LoadConstString>(); DexReferenceCache cache = ctx.getDexReferenceCache(); Set<String> strings = cache.listConstString(); for (String value : strings) { Matcher matcher = pattern.matcher(value); if (matcher.find()) { for (LoadConstString cons : cache.findConstString(value)) { codeString.add(cons); } } } int sum = resSpecs.size() + codeString.size(); if (sum != 0) { LineBuilder lb = new LineBuilder(); lb.newLine(); Iterator<ResResSpec> i = resSpecs.iterator(); int cnt = 0; while (i.hasNext()) { mainFrame.actionReportWork(action, 100 * cnt++ / sum); appendSpec(lb, i.next(), mainFrame, ctx, 0, 0, 0); } appendCodeReference(codeString, ctx, lb, mainFrame); mainFrame.showText("Label found for " + label, lb); mainFrame.setBottomInfo(sum + " label(s) found"); } else { mainFrame.setBottomInfo("No label found"); } } public void showSystemReference(ApkClassContext ctx, MainFrame mainFrame, AbstractCanceableAction action) { DexReferenceCache cache = ctx.getDexReferenceCache(); HashSet<ResResSpec> specs = cache.findResAndroidSystemReference(); ArrayList<XmlLine> xmllines = ctx.getXmlParser().findXmlAndroidSystemReference(); ArrayList<LoadConstRes> loadconsts = cache.findCodeAndroidSystemReference(); int sum = specs.size() + xmllines.size() + loadconsts.size(); if (sum != 0) { LineBuilder lb = new LineBuilder(); appendBagReference(specs, ctx, lb, mainFrame); appendXmlReference(xmllines, ctx, lb, mainFrame); appendCodeReference(loadconsts, ctx, lb, mainFrame); mainFrame.showText("Android resource references: ", lb); mainFrame.setBottomInfo(sum + " resource(es) found"); } else { mainFrame.setBottomInfo("No android package reference found"); } } public void showUnusedSpec(ApkClassContext ctx, MainFrame mainFrame, AbstractCanceableAction action) { //long starttime = System.currentTimeMillis(); DexReferenceCache cache = ctx.getDexReferenceCache(); ArrayList<ResResSpec> resSpecs = new ArrayList<ResResSpec>(); ArrayList<ResResSpec> usedSpecs = new ArrayList<ResResSpec>(); ArrayList<ResResSpec> unusedSpecs = new ArrayList<ResResSpec>(); ArrayList<Integer> usedRef = new ArrayList<Integer>(); for (ResPackage pkg : mResTable.listMainPackages()) { for (ResResSpec spec : pkg.listResSpecs()) { resSpecs.add(spec); } } if (resSpecs != null && resSpecs.size() > 0) { Comparator<ResResSpec> comp = new SpecComparator(); Collections.sort(resSpecs, comp); for (int i = 0; i < resSpecs.size(); i++) { DexResSpec dexSpec = cache.getDexSpec(resSpecs.get(i)); if (dexSpec.getSumReference() == 0) { unusedSpecs.add(resSpecs.get(i)); } else { usedSpecs.add(resSpecs.get(i)); usedRef.add(dexSpec.getCountSpecReference()); usedRef.add(dexSpec.getCountXmlReference()); usedRef.add(dexSpec.getCountCodeReference()); } mainFrame.actionReportWork(action, 100 * i / resSpecs.size()); } LineBuilder lb = new LineBuilder(); boolean first = true; for (ResResSpec unusedSpec : unusedSpecs) { if (first) { lb.newLine(); lb.append("UNUSED RESOURCES: ", COLOR_ERROR); lb.newLine(); first = false; } /* lb.append(String.format("echo %s %s %s >> 1234.log",unusedSpec.getName(), unusedSpec.getType().getName() ,String.format("%08X",unusedSpec.getId().id)), COLOR_COMMENT); lb.newLine(); lb.append(String.format("find contacts-largeui -type f -exec grep \"%s\" -H {} \\; >> 1234.log",unusedSpec.getName()), COLOR_COMMENT); lb.newLine(); lb.append(String.format("echo \"\" >> 1234.log",unusedSpec.getName()), COLOR_COMMENT); lb.newLine(); */ appendSpec(lb, unusedSpec, mainFrame, ctx, 0, 0, 0); } if (first != true) { lb.newLine(); } first = true; for (int i = 0; i < usedSpecs.size(); i++) { if (first) { lb.newLine(); lb.append("USED RESOURCES: ", COLOR_ERROR); lb.newLine(); first = false; } appendSpec(lb, usedSpecs.get(i), mainFrame, ctx, usedRef.get(i * 3), usedRef.get(i * 3 + 1), usedRef.get(i * 3 + 2)); } mainFrame.showText("Resource unused IDs: ", lb); mainFrame.setBottomInfo(resSpecs.size() + " unused resource(es) found"); } else { mainFrame.setBottomInfo("No resource found"); } } public void showUnusedFiles(ApkClassContext ctx, RefContext cRef, MainFrame mainFrame, AbstractCanceableAction action) { DexReferenceCache cache = ctx.getDexReferenceCache(); ArrayList<ResResSpec> resSpecs = new ArrayList<ResResSpec>(); ArrayList<ResResSpec> usedSpecs = new ArrayList<ResResSpec>(); ArrayList<ResResSpec> unusedSpecs = new ArrayList<ResResSpec>(); ArrayList<ResResSpec> resultSpecs = new ArrayList<ResResSpec>(); HashMap<ResResSpec, ArrayList<String>> specFileMap = new HashMap<ResResSpec, ArrayList<String>>(); for (ResPackage pkg : mResTable.listMainPackages()) { for (ResResSpec spec : pkg.listResSpecs()) { ArrayList<String> fileNames = new ArrayList<String>(); for (ResResource res : spec.listResources()) { if (res.getValue() instanceof ResFileValue) { fileNames.add(((ResFileValue) res.getValue()).getPath()); } } if (fileNames.size() != 0) { resSpecs.add(spec); specFileMap.put(spec, fileNames); } } } for (int i = 0; i < resSpecs.size(); i++) { if (cache.getDexSpec(resSpecs.get(i)).getSumReference() == 0) { unusedSpecs.add(resSpecs.get(i)); } else { usedSpecs.add(resSpecs.get(i)); } mainFrame.actionReportWork(action, 80 * i / resSpecs.size()); } if (unusedSpecs.size() != 0) { for (ResResSpec unusedSpec : unusedSpecs) { boolean foundInUsed = false; for (String unusedFile : specFileMap.get(unusedSpec)) { for (ResResSpec usedSpec : usedSpecs) { for (String usedFile : specFileMap.get(usedSpec)) { if (usedFile.equals(unusedFile)) { foundInUsed = true; break; } } if (foundInUsed == true) { break; } } if (foundInUsed == true) { break; } } if (foundInUsed == false) { resultSpecs.add(unusedSpec); } } } mainFrame.actionReportWork(action, 90); if (resultSpecs.size() != 0) { LineBuilder lb = new LineBuilder(); lb.newLine(); long bytes = 0; for (int i = 0; i < resultSpecs.size(); i++) { ResResSpec resultSpec = resultSpecs.get(i); for (String resultFileName : specFileMap.get(resultSpec)) { bytes += appendUnusedFileSpec(lb, resultSpec, resultFileName, mainFrame, cRef); } mainFrame.actionReportWork(action, 10 * i / resultSpecs.size() + 90); } mainFrame.showText("Unused resource files (total " + bytes + " bytes) :", lb); mainFrame.setBottomInfo(resSpecs.size() + " unused resources file(s) found"); } else { mainFrame.setBottomInfo("No unused resource files found"); } } private long appendUnusedFileSpec(LineBuilder lb, ResResSpec resSpec, String fileName, MainFrame mainFrame, RefContext cRef) { long size = 0; ZipEntry entry = mParser.visitFile(fileName); if (entry != null) { size = entry.getSize(); lb.append(String.format("%08X", resSpec.getId().id), COLOR_KEYWORD); lb.append(String.format(" %s ", resSpec.getType().getName()), COLOR_HEX); lb.append(String.format(" %s ", entry.getName()), COLOR_COMMENT); lb.append(String.format(" (%d bytes) ", entry.getSize()), COLOR_SYMBOL); Object[] data = { resSpec, cRef }; lb.setReferenceToCurrent(new LineBuilderFormatter.Link( XmlViewReferenceAction.getInstance(mainFrame), data)); lb.newLine(); } return size; } public void showAllResSpec(ApkClassContext ctx, MainFrame mainFrame, AbstractCanceableAction action) { ArrayList<ResResSpec> resSpecs = new ArrayList<ResResSpec>(); for (ResPackage pkg : mResTable.listMainPackages()) { for (ResResSpec spec : pkg.listResSpecs()) { resSpecs.add(spec); } } if (resSpecs != null && resSpecs.size() > 0) { Comparator<ResResSpec> comp = new SpecComparator(); Collections.sort(resSpecs, comp); LineBuilder lb = new LineBuilder(); lb.newLine(); for (int i = 0; i < resSpecs.size(); i++) { mainFrame.actionReportWork(action, 100 * i / resSpecs.size()); appendSpec(lb, resSpecs.get(i), mainFrame, ctx, 0, 0, 0); } mainFrame.showText("Resource IDs: ", lb); mainFrame.setBottomInfo(resSpecs.size() + " resource(es) found"); } else { mainFrame.setBottomInfo("No resource found"); } } public LineBuilder showSpecDetail(ResResSpec spec, MainFrame mainFrame, ApkClassContext ctx) { DexReferenceCache cache = ctx.getDexReferenceCache(); DexResSpec dexSpec = cache.getDexSpec(spec); int issue = dexSpec.getIssue(); LineBuilder lb = new LineBuilder(); lb.newLine(); lb.append("ID: ", COLOR_KEYWORD); lb.append(String.format("%08X", spec.getId().id), COLOR_TEXT); lb.newLine(); lb.append("NAME: ", COLOR_KEYWORD); lb.append(spec.getName(), COLOR_TEXT); lb.newLine(); lb.append("PACKAGE: ", COLOR_KEYWORD); lb.append(spec.getPackage().getName(), COLOR_TEXT); lb.newLine(); lb.append("TYPE: ", COLOR_KEYWORD); lb.append(spec.getType().getName(), COLOR_TEXT); lb.newLine(); if (issue != 0) { lb.newLine(); lb.append("ISSUE: ", COLOR_KEYWORD); lb.newLine(); if ((issue & DexResSpec.ISSUE_MISSING_RESOURCE) != 0) { lb.append(DexResSpec.getIssueName(DexResSpec.ISSUE_MISSING_RESOURCE), COLOR_ERROR); lb.newLine(); } if ((issue & DexResSpec.ISSUE_NO_DPI) != 0) { lb.append(DexResSpec.getIssueName(DexResSpec.ISSUE_NO_DPI), COLOR_ERROR); lb.newLine(); } if ((issue & DexResSpec.ISSUE_NO_DEFAULT) != 0) { lb.append(DexResSpec.getIssueName(DexResSpec.ISSUE_NO_DEFAULT), COLOR_ERROR); lb.newLine(); } if ((issue & DexResSpec.ISSUE_MISS_LANGUAGE) != 0) { lb.append(DexResSpec.getIssueName(DexResSpec.ISSUE_MISS_LANGUAGE), COLOR_ERROR); lb.newLine(); } } if (spec.hasDefaultResource()) { lb.newLine(); lb.append("DEFAULT RESOURCE: ", COLOR_KEYWORD); lb.newLine(); try { ResValue defValue = spec.getDefaultResource().getValue(); //each line should have newline otherwise setCurrentReference will not work. String content = getValue(defValue, "\n", false); String[] sArray = content.split("\n"); for (String oneLine : sArray) { lb.append(oneLine, (defValue instanceof ResFileValue) ? COLOR_HEX : COLOR_TEXT); if (defValue instanceof ResFileValue) { String path = ((ResFileValue) defValue).getPath(); if (path.toLowerCase().endsWith(".xml")) { Object[] data = { ctx, ((ResFileValue) defValue).getPath(), new Integer(-1), new Integer(-1), new Boolean(false) }; lb.setReferenceToCurrent(new LineBuilderFormatter.Link( XmlViewerAction.getInstance(mainFrame), data)); } } lb.newLine(); } } catch (AndrolibException e) { e.printStackTrace(); } lb.newLine(); } lb.newLine(); lb.append("CONFIGS: ", COLOR_KEYWORD); lb.newLine(); for (ResResource res : spec.listResources()) { lb.append(res.getConfig().toString()); lb.append(" "); lb.append(getValue(res.getValue(), " ", false), (res.getValue() instanceof ResFileValue) ? COLOR_HEX : COLOR_TEXT); if (res.getValue() instanceof ResFileValue) { String path = ((ResFileValue) res.getValue()).getPath(); if (path.toLowerCase().endsWith(".xml")) { Object[] data = { ctx, path, new Integer(-1), new Integer(-1), new Boolean(false) }; lb.setReferenceToCurrent(new LineBuilderFormatter.Link( XmlViewerAction.getInstance(mainFrame), data)); } } lb.newLine(); } if (atLeastOneLanguage(spec)) { lb.newLine(); lb.append("LANGUAGE: ", COLOR_KEYWORD); lb.newLine(); HashSet<String> have = new HashSet<String>(); Iterator<String> i = getLanguageSet().iterator(); while (i.hasNext()) { String language = i.next(); for (ResResource res : spec.listResources()) { if (res.getConfig().getFlags().getQualifiers().indexOf(language) != -1) { have.add(language); } } } HashSet<String> miss = new HashSet<String>(); Iterator<String> ii = getLanguageSet().iterator(); while (ii.hasNext()) { String language = ii.next(); if (!have.contains(language)) { miss.add(language); } } lb.append("CONTAINS: ", COLOR_COMMENT); lb.append(have.toString().equals("[]") ? "NONE" : have.toString(), COLOR_TEXT); lb.newLine(); lb.append("MISSING: ", COLOR_COMMENT); lb.append(miss.toString().equals("[]") ? "NONE" : miss.toString(), COLOR_TEXT); lb.newLine(); } if (dexSpec.getSumReference() == 0) { lb.newLine(); lb.append("NO REFERENCE", COLOR_KEYWORD); } else { appendBagReference(dexSpec.getResReference(), ctx, lb, mainFrame); appendXmlReference(dexSpec.getXmlReference(), ctx, lb, mainFrame); appendCodeReference(dexSpec.getCodeReference(), ctx, lb, mainFrame); } return lb; } public void showIssueSpec(String density, MainFrame mainFrame, RefContext cRef, AbstractCanceableAction action, ApkClassContext ctx) { //this step without output just show it in log verifyResourceRARSC(cRef); DexReferenceCache cache = ctx.getDexReferenceCache(); mainFrame.actionReportWork(action, 10); ArrayList<ResResSpec> resSpecs = verify(density, cache); mainFrame.actionReportWork(action, 20); if (resSpecs == null || resSpecs.size() == 0) { mainFrame.setBottomInfo("No resource issue found"); return; } LineBuilder lb = new LineBuilder(); lb.newLine(); showOneIssueSpec(resSpecs, mainFrame, ctx, DexResSpec.ISSUE_MISSING_RESOURCE, "MISSING RESOURCE:", lb); mainFrame.actionReportWork(action, 40); showOneIssueSpec(resSpecs, mainFrame, ctx, DexResSpec.ISSUE_NO_DPI, "NO DPI SPECIFIED:", lb); mainFrame.actionReportWork(action, 60); showOneIssueSpec(resSpecs, mainFrame, ctx, DexResSpec.ISSUE_NO_DEFAULT, "NO DEFAULT RESOURCE:", lb); mainFrame.actionReportWork(action, 80); showOneIssueSpec(resSpecs, mainFrame, ctx, DexResSpec.ISSUE_MISS_LANGUAGE, "MISSING SOME LANGUAGE TRANSLATION:", lb); mainFrame.actionReportWork(action, 100); mainFrame.showText("Resource Issues: ", lb); mainFrame.setBottomInfo(resSpecs.size() + " issue(s) found"); } //////////////////////////////////////////////////////////////////////////////////////// // private methods /* private boolean isReferredId(int id, ResValue resValue, boolean onlyPackage){ boolean ret = false; if (resValue != null && resValue instanceof ResReferenceValue){ int value = ((ResReferenceValue)resValue).getValue(); if (onlyPackage){ ret = (new ResID(value).package_ == new ResID(id).package_); }else{ ret = (value == id); } } return ret; } */ private void appendBagReference(Set<ResResSpec> specs, ApkClassContext ctx, LineBuilder lb, MainFrame mainFrame) { boolean first = true; if (specs.size() != 0 && lb != null && mainFrame != null) { for (ResResSpec spec : specs) { if (first) { lb.newLine(); lb.append("RESOURCE INTERNAL REFERENCE (double-clickable):", COLOR_KEYWORD); lb.newLine(); first = false; } appendSpec(lb, spec, mainFrame, ctx, 0, 0, 0); } } } private void appendXmlReference(List<XmlLine> xmllines, ApkClassContext ctx, LineBuilder lb, MainFrame mainFrame) { boolean first = true; if (xmllines.size() != 0 && lb != null && mainFrame != null) { for (XmlLine xmlline : xmllines) { if (first) { lb.newLine(); lb.append("XML REFERENCE (double-clickable):", COLOR_KEYWORD); lb.newLine(); first = false; } lb.append(xmlline.entry.getName(), 0x000000); lb.append(" line "); lb.append(xmlline.line, LineBuilderFormatter.COLOR_COMMENT); Object[] data = { ctx, xmlline.entry.getName(), xmlline.line, new Integer(xmlline.id), new Boolean(false) }; lb.setReferenceToCurrent(new LineBuilderFormatter.Link( XmlViewerAction.getInstance(mainFrame), data)); lb.newLine(); } } } private void appendCodeReference(List<? extends DexReferenceCache.LoadConst> loadConsts, ApkClassContext ctx, LineBuilder lb, MainFrame mainFrame) { boolean first = true; if (loadConsts.size() != 0 && lb != null && mainFrame != null) { for (DexReferenceCache.LoadConst e : loadConsts) { if (first) { lb.newLine(); lb.append("CODE REFERENCE (double-clickable):", COLOR_KEYWORD); lb.newLine(); first = false; } if (e instanceof LoadConstRes) { lb.append(e.method.getMEClass().getName() + " : ", 0x000000); LineBuilderFormatter.makeOutline(e.method, lb); lb.append(" @ ", 0x000000); lb.append(Integer.toHexString(e.instruction.codeAddress), 0x000088); if (e.instruction.line != -1) { lb.append(" (line" + e.instruction.line + ")", 0x000088); } } else { lb.append("\"" + ((LoadConstString) e).string + "\" ", 0x880000); lb.append(" @ " + e.method.getMEClass().getClassName(), 0x000000); if (e.instruction.line != -1) { lb.append(" (line" + e.instruction.line + ")", 0x000088); } } Object[] data = { e.method, new Integer(e.instruction.codeAddress), e.instruction }; lb.setReferenceToCurrent(new LineBuilderFormatter.Link( ShowBytecodeAction.getInstance(mainFrame), data)); lb.newLine(); } } } private boolean atLeastOneLanguage(ResResSpec spec) { boolean atLeastOneLanguage = false; Iterator<String> ii = getLanguageSet().iterator(); while (ii.hasNext() && atLeastOneLanguage == false) { String language = ii.next(); for (ResResource res : spec.listResources()) { if (res.getConfig().getFlags().getQualifiers().indexOf(language) != -1) { atLeastOneLanguage = true; break; } } } return atLeastOneLanguage; } private ArrayList<ResResSpec> verify(String density, DexReferenceCache cache) { ArrayList<ResResSpec> problems = new ArrayList<ResResSpec>(); HashSet<String> languageSet = getLanguageSet(); for (ResPackage pkg : mResTable.listMainPackages()) { for (ResResSpec spec : pkg.listResSpecs()) { int issue = 0; if (spec.getName().startsWith("MISSING_RESOURCE_")) { issue = issue | DexResSpec.ISSUE_MISSING_RESOURCE; } if (!spec.hasDefaultResource()) { issue = issue | DexResSpec.ISSUE_NO_DEFAULT; } String typeName = spec.getType().getName(); StringBuilder sb = new StringBuilder(); if (atLeastOneLanguage(spec) || typeName.equals("string") || typeName.equals("plurals")) { boolean hasLanguage = true; Iterator<String> i = languageSet.iterator(); while (i.hasNext()) { String language = i.next(); boolean containsLanguage = false; if (language.equals("-en") && spec.hasDefaultResource()) { containsLanguage = true; } else { for (ResResource res : spec.listResources()) { if (res.getConfig().getFlags().getQualifiers().indexOf(language) != -1) { containsLanguage = true; } } } if (!containsLanguage) { hasLanguage = false; sb.append(language); } } if (!hasLanguage) { issue = issue | DexResSpec.ISSUE_MISS_LANGUAGE; } } if ((!spec.hasDefaultResource()) && (!density.equals("nodpi"))) { boolean hasDensity = false; for (ResResource res : spec.listResources()) { if (res.getConfig().getFlags().getQualifiers().indexOf(density) != -1 || res.getConfig().getFlags().getQualifiers().indexOf("nodpi") != -1) { hasDensity = true; } } if (!hasDensity) { issue = issue | DexResSpec.ISSUE_NO_DPI; } else { issue = issue & ~DexResSpec.ISSUE_NO_DEFAULT; } } if (issue != 0) { cache.getDexSpec(spec).setIssue(issue); problems.add(spec); } } } return problems; } private void appendSpec(LineBuilder lb, ResResSpec resSpec, MainFrame mainFrame, ApkClassContext ctx, int res, int xml, int code) { lb.append(String.format("%08X", resSpec.getId().id), COLOR_KEYWORD); lb.append(String.format(" %s ", resSpec.getType().getName()), COLOR_HEX); if (res != 0) { lb.append(String.format(" [RES %d] ", res), COLOR_LABEL); } if (xml != 0) { lb.append(String.format(" [XML %d] ", xml), COLOR_LABEL); } if (code != 0) { lb.append(String.format(" [CODE %d] ", code), COLOR_LABEL); } lb.append(String.format(" %s", resSpec.getName()), COLOR_COMMENT); ResResource defaultRes = null; try { defaultRes = resSpec.getDefaultResource(); } catch (AndrolibException e) { } if (defaultRes != null) { //lb.append(String.format(" %s", defaultRes.getValue().getClass().getSimpleName()),COLOR_PC); lb.append(String.format(" %s", getValue(defaultRes.getValue(), " ", true)), (defaultRes.getValue() instanceof ResFileValue) ? COLOR_HEX : COLOR_TEXT); } else { lb.append(" NO DEFAULT", COLOR_ERROR); } Object[] data = { resSpec, ctx }; lb.setReferenceToCurrent(new LineBuilderFormatter.Link( XmlViewReferenceAction.getInstance(mainFrame), data)); lb.newLine(); } private void showOneIssueSpec(ArrayList<ResResSpec> resSpecs, MainFrame mainFrame, ApkClassContext ctx, int filter, String Title, LineBuilder lb) { boolean displayed = false; ArrayList<ResResSpec> issueSpec1 = new ArrayList<ResResSpec>(); for (ResResSpec resSpec : resSpecs) { if ((ctx.getDexReferenceCache().getDexSpec(resSpec).getIssue() & filter) != 0) { issueSpec1.add(resSpec); } } if (issueSpec1.size() != 0) { lb.append(DexResSpec.getIssueName(filter), COLOR_ERROR); lb.newLine(); for (ResResSpec spec : issueSpec1) { appendSpec(lb, spec, mainFrame, ctx, 0, 0, 0); } displayed = true; } if (displayed == true) { lb.newLine(); } } private void verifyResourceRARSC(RefContext cRef) { ArrayList<DexField> fields = new ArrayList<DexField>(); //verify resource from R.java to arsc Iterator<Reference> i = cRef.getChildren().iterator(); while (i.hasNext()) { Reference ref = i.next(); if (ref instanceof RefPackage && ((RefPackage) ref).getName().equals(getMainPackageName())) { Iterator<Reference> iclass = ref.getChildren().iterator(); while (iclass.hasNext()) { Reference refClass = iclass.next(); if (refClass instanceof RefClass && (((RefClass) refClass).getName().equals("R") || ((RefClass) refClass).getName().startsWith("R$")) && !((RefClass) refClass).getName().equals("R$styleable")) { DexClass dexClass = (DexClass) ((RefClass) refClass).getMEClass(); DexField[] dexFields = (DexField[]) dexClass.getFields(); for (DexField field : dexFields) { if (field.isPublic() && field.isStatic() && field.isFinal() && field.getDescriptor().equals("I") && field.getConstantValue() instanceof Long) { int id = ((Long) field.getConstantValue()).intValue(); try { if (mResTable.getResSpec(id) != null) { fields.add(field); } } catch (AndrolibException e) { System.out.println("ERROR: Resource field " + field.getName() + " (" + String.format("%08X", id) + ") could not be found in spec"); e.printStackTrace(); } } } } } } } //verify resource from arsc to R.java for (ResPackage pkg : mResTable.listMainPackages()) { List<ResResSpec> specs = pkg.listResSpecs(); for (ResResSpec spec : specs) { int specid = spec.getId().id; boolean found = false; for (DexField field : fields) { if (found == false) { int id = ((Long) field.getConstantValue()).intValue(); if (id == specid) { found = true; break; } } } if (found == false) { System.out.println("ERROR: Resource spec (" + String.format("%08X", specid) + ") could not be found in R.java"); } } } } private String getMainPackageName() { for (ResPackage pkg : mResTable.listMainPackages()) { return pkg.getName(); } return null; } private HashSet<String> getLanguageSet() { if (mLanguageSet == null) { mLanguageSet = new HashSet<String>(); for (ResPackage pkg : mResTable.listMainPackages()) { for (ResConfig config : pkg.getConfigs()) { ResConfigFlags flags = config.getFlags(); StringBuilder ret = new StringBuilder(); if (flags.language[0] != '\00') { ret.append('-').append(flags.language); /*if (flags.country[0] != '\00') { ret.append("-r").append(flags.country); }*/ } if (!ret.toString().equals("")) { mLanguageSet.add(ret.toString()); } } } } return mLanguageSet; } private String getValue(ResValue resValue, String seperator, boolean shortString) { String value = null; if (resValue instanceof ResReferenceValue) { value = XmlResAttrDecoder.getResReferenceValue((ResReferenceValue) resValue, mResTable, false); } else if (resValue instanceof ResStringValue || resValue instanceof ResFloatValue || resValue instanceof ResDimenValue || resValue instanceof ResFractionValue || resValue instanceof ResBoolValue || resValue instanceof ResColorValue || resValue instanceof ResIntValue) { try { value = ((ResScalarValue) resValue).encodeAsResXmlValue().replace('\n', ' '); } catch (AndrolibException e) { e.printStackTrace(); } if (shortString == true && value.length() > 50) { value = value.substring(0, 50) + "..."; } if (resValue instanceof ResStringValue && value.equals("")) { value = "EMPTY STRING"; } else { value = "\"" + value + "\""; } } else if (resValue instanceof ResFileValue) { value = ((ResFileValue) resValue).getPath(); } else if (resValue instanceof ResBagValue) { value = ""; if (((ResBagValue) resValue).getParent() != null) { String parent = getValue(((ResBagValue) resValue).getParent(), " ", shortString); if (!parent.equals("@null")) { value += "parent=" + parent + seperator; } } if (resValue instanceof ResAttr) { ResAttr attr = (ResAttr) resValue; String type = attr.getTypeAsString(); if (type != null) { value += "format=" + type + " "; } if (attr.mMin != null) { value += "min=" + attr.mMin.toString() + " "; } if (attr.mMax != null) { value += "max=" + attr.mMax.toString() + " "; } if (attr.mL10n != null && attr.mL10n) { value += "localization=suggested"; } value += " "; if (resValue instanceof ResFlagsAttr) { int length = ((ResFlagsAttr) resValue).mItems.length; for (int i = 0; i < length; i++) { FlagItem item = ((ResFlagsAttr) resValue).mItems[i]; value += getValue(item.ref, " ", shortString); value += "=" + String.format("0x%08x", item.flag); if (shortString) { break; } } if (shortString && length > 1) { value += " ... "; } } else if (resValue instanceof ResEnumAttr) { for (Duo<ResReferenceValue, ResIntValue> duoValue : ((ResEnumAttr) resValue).mItems) { String m1 = getValue(duoValue.m1, " ", shortString); value += m1 + "="; value += getValue(duoValue.m2, " ", shortString) + seperator; if (shortString) { break; } } if (shortString && ((ResEnumAttr) resValue).mItems.length > 1) { value += " ... "; } } } else if (resValue instanceof ResArrayValue) { for (ResScalarValue resScalarValue : ((ResArrayValue) resValue).mItems) { value += getValue(resScalarValue, " ", shortString) + seperator; if (shortString) { break; } } if (shortString && ((ResArrayValue) resValue).mItems.length > 1) { value += " ... "; } } else if (resValue instanceof ResPluralsValue) { for (int i = 0; i < ((ResPluralsValue) resValue).mItems.length; i++) { ResScalarValue item = ((ResPluralsValue) resValue).mItems[i]; if (item == null) { continue; } value += ResPluralsValue.QUANTITY_MAP[i] + " = " + getValue(item, " ", shortString) + seperator; if (shortString) { break; } } if (shortString) { value += " ... "; } } else if (resValue instanceof ResStyleValue) { for (Duo<ResReferenceValue, ResScalarValue> duoValue : ((ResStyleValue) resValue).mItems) { String m1 = getValue(duoValue.m1, " ", shortString); value += m1 + "="; value += getValue(duoValue.m2, " ", shortString) + seperator; if (shortString) { break; } } if (shortString && ((ResStyleValue) resValue).mItems.length > 1) { value += " ... "; } } } if (value == null) { System.out.println("[ERROR] unhandled resource value: " + resValue.toString()); } return value; } private class SpecComparator implements Comparator<ResResSpec> { @Override public int compare(ResResSpec o1, ResResSpec o2) { return o1.getId().id > o2.getId().id ? 1 : 0; } } }