Home | History | Annotate | Download | only in export
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.sdklib.internal.export;
     18 
     19 import com.android.sdklib.SdkConstants;
     20 import com.android.sdklib.internal.export.MultiApkExportHelper.ExportException;
     21 import com.android.sdklib.internal.project.ApkSettings;
     22 import com.android.sdklib.internal.project.ProjectProperties;
     23 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
     24 import com.android.sdklib.resources.Density;
     25 import com.android.sdklib.xml.ManifestData;
     26 import com.android.sdklib.xml.ManifestData.SupportsScreens;
     27 
     28 import java.io.File;
     29 import java.io.FilenameFilter;
     30 import java.util.ArrayList;
     31 import java.util.HashMap;
     32 import java.util.List;
     33 import java.util.Map;
     34 import java.util.Map.Entry;
     35 
     36 /**
     37  * Class representing an Android project and its properties.
     38  *
     39  * Only the properties that pertain to the multi-apk export are present.
     40  */
     41 public final class ProjectConfig {
     42 
     43     private static final String PROP_API = "api";
     44     private static final String PROP_SCREENS = "screens";
     45     private static final String PROP_GL = "gl";
     46     private static final String PROP_ABI = "splitByAbi";
     47     private static final String PROP_DENSITY = "splitByDensity";
     48     private static final String PROP_LOCALEFILTERS = "localeFilters";
     49 
     50     /**
     51      * List of densities and their associated aapt filter.
     52      */
     53     private static final String[][] DENSITY_LIST = new String[][] {
     54         new String[] { Density.HIGH.getResourceValue(),
     55                 Density.HIGH.getResourceValue() + "," + Density.NODPI.getResourceValue() },
     56                 new String[] { Density.MEDIUM.getResourceValue(),
     57                         Density.MEDIUM.getResourceValue() + "," +
     58                                 Density.NODPI.getResourceValue() },
     59                         new String[] { Density.MEDIUM.getResourceValue(),
     60                 Density.MEDIUM.getResourceValue() + "," + Density.NODPI.getResourceValue() },
     61     };
     62 
     63     private final File mProjectFolder;
     64     private final String mRelativePath;
     65 
     66     private final int mMinSdkVersion;
     67     private final int mGlEsVersion;
     68     private final SupportsScreens mSupportsScreens;
     69     private final boolean mSplitByAbi;
     70     private final boolean mSplitByDensity;
     71     private final Map<String, String> mLocaleFilters;
     72     /** List of ABIs not defined in the properties but actually existing in the project as valid
     73      * .so files */
     74     private final List<String> mAbis;
     75 
     76     static ProjectConfig create(File projectFolder, String relativePath,
     77             ManifestData manifestData) throws ExportException {
     78         // load the project properties
     79         ProjectProperties projectProp = ProjectProperties.load(projectFolder.getAbsolutePath(),
     80                 PropertyType.DEFAULT);
     81         if (projectProp == null) {
     82             throw new ExportException(String.format("%1$s is missing for project %2$s",
     83                     PropertyType.DEFAULT.getFilename(), relativePath));
     84         }
     85 
     86         ApkSettings apkSettings = new ApkSettings(projectProp);
     87 
     88         return new ProjectConfig(projectFolder,
     89                 relativePath,
     90                 manifestData.getMinSdkVersion(),
     91                 manifestData.getGlEsVersion(),
     92                 manifestData.getSupportsScreensValues(),
     93                 apkSettings.isSplitByAbi(),
     94                 apkSettings.isSplitByDensity(),
     95                 apkSettings.getLocaleFilters());
     96     }
     97 
     98 
     99     private ProjectConfig(File projectFolder, String relativePath,
    100             int minSdkVersion, int glEsVersion,
    101             SupportsScreens supportsScreens, boolean splitByAbi, boolean splitByDensity,
    102             Map<String, String> localeFilters) {
    103         mProjectFolder = projectFolder;
    104         mRelativePath = relativePath;
    105         mMinSdkVersion = minSdkVersion;
    106         mGlEsVersion = glEsVersion;
    107         mSupportsScreens = supportsScreens;
    108         mSplitByAbi = splitByAbi;
    109         mSplitByDensity = splitByDensity;
    110         mLocaleFilters = localeFilters;
    111         if (mSplitByAbi) {
    112             mAbis = findAbis();
    113         } else {
    114             mAbis = null;
    115         }
    116     }
    117 
    118     public File getProjectFolder() {
    119         return mProjectFolder;
    120     }
    121 
    122 
    123     public String getRelativePath() {
    124         return mRelativePath;
    125     }
    126 
    127     List<ApkData> getApkDataList() {
    128         // there are 3 cases:
    129         // 1. ABI split generate multiple apks with different build info, so they are different
    130         //    ApkData for all of them. Special case: split by abi but no native code => 1 ApkData.
    131         // 2. split by density or locale filters generate soft variant only, so they all go
    132         //    in the same ApkData.
    133         // 3. Both 1. and 2. means that more than one ApkData are created and they all get soft
    134         //    variants.
    135 
    136         ArrayList<ApkData> list = new ArrayList<ApkData>();
    137 
    138         Map<String, String> softVariants = computeSoftVariantMap();
    139 
    140         if (mSplitByAbi) {
    141             if (mAbis.size() > 0) {
    142                 for (String abi : mAbis) {
    143                     list.add(new ApkData(this, abi, softVariants));
    144                 }
    145             } else {
    146                 // if there are no ABIs, then just generate a single ApkData with no specific ABI.
    147                 list.add(new ApkData(this, softVariants));
    148             }
    149         } else {
    150             // create a single ApkData.
    151             list.add(new ApkData(this, softVariants));
    152         }
    153 
    154         return list;
    155     }
    156 
    157     int getMinSdkVersion() {
    158         return mMinSdkVersion;
    159     }
    160 
    161     SupportsScreens getSupportsScreens() {
    162         return mSupportsScreens;
    163     }
    164 
    165     int getGlEsVersion() {
    166         return mGlEsVersion;
    167     }
    168 
    169     boolean isSplitByDensity() {
    170         return mSplitByDensity;
    171     }
    172 
    173     boolean isSplitByAbi() {
    174         return mSplitByAbi;
    175     }
    176 
    177     /**
    178      * Returns a map of pair values (apk name suffix, aapt res filter) to be used to generate
    179      * multiple soft apk variants.
    180      */
    181     private Map<String, String> computeSoftVariantMap() {
    182         HashMap<String, String> map = new HashMap<String, String>();
    183 
    184         if (mSplitByDensity && mLocaleFilters.size() > 0) {
    185             for (String[] density : DENSITY_LIST) {
    186                 for (Entry<String,String> entry : mLocaleFilters.entrySet()) {
    187                     map.put(density[0] + "-" + entry.getKey(),
    188                             density[1] + "," + entry.getValue());
    189                 }
    190             }
    191 
    192         } else if (mSplitByDensity) {
    193             for (String[] density : DENSITY_LIST) {
    194                 map.put(density[0], density[1]);
    195             }
    196 
    197         } else if (mLocaleFilters.size() > 0) {
    198             map.putAll(mLocaleFilters);
    199 
    200         }
    201 
    202         return map;
    203     }
    204 
    205     /**
    206      * Finds ABIs in a project folder. This is based on the presence of libs/<abi>/ folder.
    207      *
    208      * @param projectPath The OS path of the project.
    209      * @return A new non-null, possibly empty, list of ABI strings.
    210      */
    211     private List<String> findAbis() {
    212         ArrayList<String> abiList = new ArrayList<String>();
    213         File libs = new File(mProjectFolder, SdkConstants.FD_NATIVE_LIBS);
    214         if (libs.isDirectory()) {
    215             File[] abis = libs.listFiles();
    216             for (File abi : abis) {
    217                 if (abi.isDirectory()) {
    218                     // only add the abi folder if there are .so files in it.
    219                     String[] content = abi.list(new FilenameFilter() {
    220                         public boolean accept(File dir, String name) {
    221                             return name.toLowerCase().endsWith(".so");
    222                         }
    223                     });
    224 
    225                     if (content.length > 0) {
    226                         abiList.add(abi.getName());
    227                     }
    228                 }
    229             }
    230         }
    231 
    232         return abiList;
    233     }
    234 
    235     String getConfigString(boolean onlyManifestData) {
    236         StringBuilder sb = new StringBuilder();
    237         LogHelper.write(sb, PROP_API, mMinSdkVersion);
    238         LogHelper.write(sb, PROP_SCREENS, mSupportsScreens.getEncodedValues());
    239 
    240         if (mGlEsVersion != ManifestData.GL_ES_VERSION_NOT_SET) {
    241             LogHelper.write(sb, PROP_GL, "0x" + Integer.toHexString(mGlEsVersion));
    242         }
    243 
    244         if (onlyManifestData == false) {
    245             if (mSplitByAbi) {
    246                 // need to not only encode true, but also the list of ABIs that will be used when
    247                 // the project is exported. This is because the hard property is not so much
    248                 // whether an apk is generated per ABI, but *how many* of them (since they all take
    249                 // a different build Info).
    250                 StringBuilder value = new StringBuilder(Boolean.toString(true));
    251                 for (String abi : mAbis) {
    252                     value.append('|').append(abi);
    253                 }
    254                 LogHelper.write(sb, PROP_ABI, value);
    255             } else {
    256                 LogHelper.write(sb, PROP_ABI, false);
    257             }
    258 
    259             // in this case we're simply always going to make 3 versions (which may not make sense)
    260             // so the boolean is enough.
    261             LogHelper.write(sb, PROP_DENSITY, Boolean.toString(mSplitByDensity));
    262 
    263             if (mLocaleFilters.size() > 0) {
    264                 LogHelper.write(sb, PROP_LOCALEFILTERS, ApkSettings.writeLocaleFilters(mLocaleFilters));
    265             }
    266         }
    267 
    268         return sb.toString();
    269     }
    270 
    271     /**
    272      * Compares the current project config to a list of properties.
    273      * These properties are in the format output by {@link #getConfigString()}.
    274      * @param values the properties to compare to.
    275      * @return null if the properties exactly match the current config, an error message otherwise
    276      */
    277     String compareToProperties(Map<String, String> values) {
    278         String tmp;
    279         // Note that most properties must always be present in the map.
    280         try {
    281             // api must always be there
    282             if (mMinSdkVersion != Integer.parseInt(values.get(PROP_API))) {
    283                 return "Attribute minSdkVersion changed";
    284             }
    285         } catch (NumberFormatException e) {
    286             // failed to convert an integer? consider the configs not equal.
    287             return "Failed to convert attribute minSdkVersion to an Integer";
    288         }
    289 
    290         try {
    291             tmp = values.get(PROP_GL); // GL is optional in the config string.
    292             if (tmp != null) {
    293                 if (mGlEsVersion != Integer.decode(tmp)) {
    294                     return "Attribute glEsVersion changed";
    295                 }
    296             }
    297         } catch (NumberFormatException e) {
    298             // failed to convert an integer? consider the configs not equal.
    299             return "Failed to convert attribute glEsVersion to an Integer";
    300         }
    301 
    302         tmp = values.get(PROP_DENSITY);
    303         if (tmp == null || mSplitByDensity != Boolean.valueOf(tmp)) {
    304             return "Property split.density changed or is missing from config file";
    305         }
    306 
    307         // compare the ABI. If splitByAbi is true, then compares the ABIs present in the project
    308         // as they must match.
    309         tmp = values.get(PROP_ABI);
    310         if (tmp == null) {
    311             return "Property split.abi is missing from config file";
    312         }
    313         String[] abis = tmp.split("\\|");
    314         if (mSplitByAbi != Boolean.valueOf(abis[0])) { // first value is always the split boolean
    315             return "Property split.abi changed";
    316         }
    317         // now compare the rest if needed.
    318         if (mSplitByAbi) {
    319             if (abis.length - 1 != mAbis.size()) {
    320                 return "The number of ABIs available in the project changed";
    321             }
    322             for (int i = 1 ; i < abis.length ; i++) {
    323                 if (mAbis.indexOf(abis[i]) == -1) {
    324                     return "The list of ABIs available in the project changed";
    325                 }
    326             }
    327         }
    328 
    329         tmp = values.get(PROP_SCREENS);
    330         if (tmp != null) {
    331             SupportsScreens supportsScreens = new SupportsScreens(tmp);
    332             if (supportsScreens.equals(mSupportsScreens) == false) {
    333                 return "Supports-Screens value changed";
    334             }
    335         } else {
    336             return "Supports-screens value missing from config file";
    337         }
    338 
    339         tmp = values.get(PROP_LOCALEFILTERS);
    340         if (tmp != null) {
    341             if (mLocaleFilters.equals(ApkSettings.readLocaleFilters(tmp)) == false) {
    342                 return "Locale resource filter changed";
    343             }
    344         } else {
    345             // do nothing. locale filter is optional in the config string.
    346         }
    347 
    348         return null;
    349     }
    350 }
    351