/* * Copyright (C) 2012 The Android Open Source Project * * 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 com.motorolamobility.preflighting.core.applicationdata; import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import com.motorolamobility.preflighting.core.source.model.Constant; import com.motorolamobility.preflighting.core.source.model.Field; import com.motorolamobility.preflighting.core.source.model.Instruction; import com.motorolamobility.preflighting.core.source.model.Invoke; import com.motorolamobility.preflighting.core.source.model.Method; import com.motorolamobility.preflighting.core.source.model.SourceFileElement; /** * Folder that contains source code inside the Android application project. */ public class SourceFolderElement extends FolderElement { private static final String R_JAVA = "R.java"; private static final String R$ID = "R$id"; private static final String R$LAYOUT = "R$layout"; private static final String R$STRING = "R$string"; private List<SourceFileElement> sourceFileElements = new ArrayList<SourceFileElement>(); private final boolean isApk; /** * Constructor which loads the minimum necessary data. * * @param folder {@link File} which represents the folder. * @param parent {@link Element} that is the parent in the tree of the Android project representation. * @param isApk <code>true</code> if dealing with APK, <code>false</code> if dealing with Project. */ public SourceFolderElement(File folder, Element parent, boolean isApk) { super(folder, parent, Element.Type.FOLDER_SRC); this.isApk = isApk; } /** * Gets the list of {@link SourceFileElement} objects inside this folder. * * @return Returns the list of {@link SourceFileElement} inside the folder. */ public List<SourceFileElement> getSourceFileElements() { return sourceFileElements; } /** * Get the list of {@link Invoke} which is inside each {@link SourceFileElement} * of this folder. * * @return returns the list of invoked methods inside each {@link SourceFileElement}. */ public List<Invoke> getInvokedMethods() { List<Invoke> invokedMethods = new ArrayList<Invoke>(); for (SourceFileElement smali : sourceFileElements) { List<Method> virtualMethods = smali.getVirtualMethods(); List<Method> directMethods = smali.getDirectMethods(); extractMethodsInvoked(invokedMethods, directMethods); extractMethodsInvoked(invokedMethods, virtualMethods); } return invokedMethods; } /** * Lists all methods invoked by a APK, given a list of methods. * * @param invokedMethodsInsideProject List of {@link Invoke} methods inside * the project. * @param methods List of methods where the search will be done. */ private void extractMethodsInvoked(List<Invoke> invokedMethodsInsideProject, List<Method> methods) { for (Method m : methods) { List<Instruction> instructions = m.getInstructions(); for (Instruction instr : instructions) { if (instr instanceof Invoke) { Invoke invoke = (Invoke) instr; invokedMethodsInsideProject.add(invoke); } } } } /** * Gets all Ids declared in R.java. * * @return Returns the {@link List} of Ids within R.Java, represented by {@link Field} objects. */ public List<Field> getIds() { List<Field> ids = new ArrayList<Field>(); for (SourceFileElement smali : sourceFileElements) { if (R_JAVA.equals(smali.getSourceName())) { List<Field> staticFields = smali.getStaticFields(); if (smali.getClassFullPath().contains(R$ID)) { ids.addAll(smali.getStaticFields()); break; } else { for (Field field : staticFields) { if (field.getName().startsWith("id")) { ids.add(field); } } } } } return ids; } /** * Get layouts declared in R.java. * * @return Returns the list of {@link Field} representing the * layouts declared in R.java. */ public List<Field> getLayouts() { List<Field> layouts = new ArrayList<Field>(); if (isApk) { if (sourceFileElements != null) { for (SourceFileElement smali : sourceFileElements) { if (R_JAVA.equals(smali.getSourceName())) { List<Field> staticFields = smali.getStaticFields(); if (smali.getClassFullPath().contains(R$LAYOUT)) { layouts.addAll(staticFields); break; } else { for (Field field : staticFields) { if (field.getName().startsWith("layout")) { layouts.add(field); } } } } } } } return layouts; } /** * Get strings declared in R.java. * * @return the list of {@link Field} objects representing the strings declared in R.java. */ public List<Field> getStrings() { List<Field> strings = new ArrayList<Field>(); if (isApk) { if (sourceFileElements != null) { for (SourceFileElement smali : sourceFileElements) { if (R_JAVA.equals(smali.getSourceName())) { List<Field> staticFields = smali.getStaticFields(); if (smali.getClassFullPath().contains(R$STRING)) { strings.addAll(staticFields); break; } else { for (Field field : staticFields) { if (field.getName().startsWith("string")) { strings.add(field); } } } } } } } return strings; } /** * Gets the layouts that are used in the code. * (it makes the relationship among const command * and the reference inside R.java) * * @return Returns the list of layouts used in the code. */ private List<Field> getLayoutsUsedInCode() { List<Field> layoutsUsed = new ArrayList<Field>(); List<Field> layoutsDeclared = getLayouts(); if (isApk) { if (sourceFileElements != null) { for (SourceFileElement smali : sourceFileElements) { List<Method> virtualMethods = smali.getVirtualMethods(); List<Method> directMethods = smali.getDirectMethods(); extractLayoutsUsed(layoutsUsed, layoutsDeclared, directMethods); extractLayoutsUsed(layoutsUsed, layoutsDeclared, virtualMethods); } } } return layoutsUsed; } private void extractLayoutsUsed(List<Field> layoutsUsed, List<Field> layoutsDeclared, List<Method> methods) { for (Method m : methods) { List<Instruction> instructions = m.getInstructions(); for (Instruction instr : instructions) { if (instr instanceof Constant) { Constant constant = (Constant) instr; String value = constant.getValue(); for (Field f : layoutsDeclared) { //find layout being used - unfortunately it may be false positive if ((value != null) && value.equals(f.getValue())) { layoutsUsed.add(f); } } } } } } /** * Gets the strings that are used in the code. * (it makes the relationship among const command * and the reference inside R.java) * * @return Returns the list of layouts used in the code. */ private List<Field> getStringsUsedInCode() { List<Field> stringsUsed = new ArrayList<Field>(); List<Field> stringsDeclared = getStrings(); if (isApk) { if (sourceFileElements != null) { for (SourceFileElement smali : sourceFileElements) { List<Method> virtualMethods = smali.getVirtualMethods(); List<Method> directMethods = smali.getDirectMethods(); extractStringsUsed(stringsUsed, stringsDeclared, directMethods); extractStringsUsed(stringsUsed, stringsDeclared, virtualMethods); } } } return stringsUsed; } private void extractStringsUsed(List<Field> stringsUsed, List<Field> stringsDeclared, List<Method> methods) { for (Method m : methods) { List<Instruction> instructions = m.getInstructions(); for (Instruction instr : instructions) { if (instr instanceof Constant) { Constant constant = (Constant) instr; String value = constant.getValue(); for (Field f : stringsDeclared) { //find layout being used - unfortunately it may be false positive if ((value != null) && value.equals(f.getValue())) { stringsUsed.add(f); } } } } } } /** * Gets all the strings used in Java files inside the source folder * @return the set of strings used in the Java files (identified by string ID, not including "R.string") */ public Set<String> getUsedStringConstants() { Set<String> usedStrings = new HashSet<String>(); if (sourceFileElements != null) { for (SourceFileElement source : sourceFileElements) { if (!isApk) { //project if (source.getUsedStringConstants() != null) { usedStrings.addAll(source.getUsedStringConstants()); } } else { //APK List<Field> fields = getStringsUsedInCode(); if (fields != null) { for (Field f : fields) { String aux = f.getName().replace("string.", ""); usedStrings.add(aux); } } } } } return usedStrings; } /** * Gets all layouts used in Java files inside the source folder * @return the set of layouts used in the Java files (identified by layout ID, not including "R.layout") */ public Set<String> getUsedLayoutConstants() { Set<String> usedLayouts = new HashSet<String>(); for (SourceFileElement source : sourceFileElements) { if (!isApk) { if (source.getUsedLayoutConstants() != null) { usedLayouts.addAll(source.getUsedLayoutConstants()); } } else { List<Field> fields = getLayoutsUsedInCode(); if (fields != null) { for (Field f : fields) { String aux = f.getName().replace("layout.", ""); usedLayouts.add(f.getName()); } } } } return usedLayouts; } /** * Clear Source File elements. */ @Override public void clean() { super.clean(); sourceFileElements.clear(); sourceFileElements = null; } /** * This implementation provides a human-readable text of this * {@link SourceFileElement}. * * @return Returns a human-readable text of this {@link SourceFileElement}. * * @see Object#toString() */ @Override public String toString() { return "ProjectJavaModel [sourceFileElements=" + sourceFileElements + "]"; } }