• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
create(File projectFolder, String relativePath, ManifestData manifestData)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 
ProjectConfig(File projectFolder, String relativePath, int minSdkVersion, int glEsVersion, SupportsScreens supportsScreens, boolean splitByAbi, boolean splitByDensity, Map<String, String> localeFilters)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 
getProjectFolder()118     public File getProjectFolder() {
119         return mProjectFolder;
120     }
121 
122 
getRelativePath()123     public String getRelativePath() {
124         return mRelativePath;
125     }
126 
getApkDataList()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 
getMinSdkVersion()157     int getMinSdkVersion() {
158         return mMinSdkVersion;
159     }
160 
getSupportsScreens()161     SupportsScreens getSupportsScreens() {
162         return mSupportsScreens;
163     }
164 
getGlEsVersion()165     int getGlEsVersion() {
166         return mGlEsVersion;
167     }
168 
isSplitByDensity()169     boolean isSplitByDensity() {
170         return mSplitByDensity;
171     }
172 
isSplitByAbi()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      */
computeSoftVariantMap()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      */
findAbis()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 
getConfigString(boolean onlyManifestData)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      */
compareToProperties(Map<String, String> values)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