• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.settings;
18 
19 import android.support.annotation.VisibleForTesting;
20 import android.text.TextUtils;
21 import android.util.Log;
22 import android.util.Xml;
23 
24 import org.xmlpull.v1.XmlPullParser;
25 import org.xmlpull.v1.XmlPullParserException;
26 
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileNotFoundException;
30 import java.io.FileReader;
31 import java.io.IOException;
32 import java.io.InputStreamReader;
33 import java.io.PrintWriter;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.zip.GZIPInputStream;
40 
41 /**
42  * The utility class that generate a license html file from xml files.
43  * All the HTML snippets and logic are copied from build/make/tools/generate-notice-files.py.
44  *
45  * TODO: Remove duplicate codes once backward support ends.
46  */
47 class LicenseHtmlGeneratorFromXml {
48     private static final String TAG = "LicenseHtmlGeneratorFromXml";
49 
50     private static final String TAG_ROOT = "licenses";
51     private static final String TAG_FILE_NAME = "file-name";
52     private static final String TAG_FILE_CONTENT = "file-content";
53     private static final String ATTR_CONTENT_ID = "contentId";
54 
55     private static final String HTML_HEAD_STRING =
56             "<html><head>\n" +
57             "<style type=\"text/css\">\n" +
58             "body { padding: 0; font-family: sans-serif; }\n" +
59             ".same-license { background-color: #eeeeee;\n" +
60             "                border-top: 20px solid white;\n" +
61             "                padding: 10px; }\n" +
62             ".label { font-weight: bold; }\n" +
63             ".file-list { margin-left: 1em; color: blue; }\n" +
64             "</style>\n" +
65             "</head>" +
66             "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n" +
67             "<div class=\"toc\">\n" +
68             "<ul>";
69 
70     private static final String HTML_MIDDLE_STRING =
71             "</ul>\n" +
72             "</div><!-- table of contents -->\n" +
73             "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">";
74 
75     private static final String HTML_REAR_STRING =
76             "</table></body></html>";
77 
78     private final List<File> mXmlFiles;
79 
80     /*
81      * A map from a file name to a content id (MD5 sum of file content) for its license.
82      * For example, "/system/priv-app/TeleService/TeleService.apk" maps to
83      * "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum
84      * of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2.
85      */
86     private final Map<String, String> mFileNameToContentIdMap = new HashMap();
87 
88     /*
89      * A map from a content id (MD5 sum of file content) to a license file content.
90      * For example, "9645f39e9db895a4aa6e02cb57294595" maps to the content string of
91      * packages/services/Telephony/MODULE_LICENSE_APACHE2. Here "9645f39e9db895a4aa6e02cb57294595"
92      * is a MD5 sum of the file content.
93      */
94     private final Map<String, String> mContentIdToFileContentMap = new HashMap();
95 
96     static class ContentIdAndFileNames {
97         final String mContentId;
98         final List<String> mFileNameList = new ArrayList();
99 
ContentIdAndFileNames(String contentId)100         ContentIdAndFileNames(String contentId) {
101             mContentId = contentId;
102         }
103     }
104 
LicenseHtmlGeneratorFromXml(List<File> xmlFiles)105     private LicenseHtmlGeneratorFromXml(List<File> xmlFiles) {
106         mXmlFiles = xmlFiles;
107     }
108 
generateHtml(List<File> xmlFiles, File outputFile)109     public static boolean generateHtml(List<File> xmlFiles, File outputFile) {
110         LicenseHtmlGeneratorFromXml genertor = new LicenseHtmlGeneratorFromXml(xmlFiles);
111         return genertor.generateHtml(outputFile);
112     }
113 
generateHtml(File outputFile)114     private boolean generateHtml(File outputFile) {
115         for (File xmlFile : mXmlFiles) {
116             parse(xmlFile);
117         }
118 
119         if (mFileNameToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) {
120             return false;
121         }
122 
123         PrintWriter writer = null;
124         try {
125             writer = new PrintWriter(outputFile);
126 
127             generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer);
128 
129             writer.flush();
130             writer.close();
131             return true;
132         } catch (FileNotFoundException | SecurityException e) {
133             Log.e(TAG, "Failed to generate " + outputFile, e);
134 
135             if (writer != null) {
136                 writer.close();
137             }
138             return false;
139         }
140     }
141 
parse(File xmlFile)142     private void parse(File xmlFile) {
143         if (xmlFile == null || !xmlFile.exists() || xmlFile.length() == 0) {
144             return;
145         }
146 
147         InputStreamReader in = null;
148         try {
149             if (xmlFile.getName().endsWith(".gz")) {
150                 in = new InputStreamReader(new GZIPInputStream(new FileInputStream(xmlFile)));
151             } else {
152                 in = new FileReader(xmlFile);
153             }
154 
155             parse(in, mFileNameToContentIdMap, mContentIdToFileContentMap);
156 
157             in.close();
158         } catch (XmlPullParserException | IOException e) {
159             Log.e(TAG, "Failed to parse " + xmlFile, e);
160             if (in != null) {
161                 try {
162                     in.close();
163                 } catch (IOException ie) {
164                     Log.w(TAG, "Failed to close " + xmlFile);
165                 }
166             }
167         }
168     }
169 
170     /*
171      * Parses an input stream and fills a map from a file name to a content id for its license
172      * and a map from a content id to a license file content.
173      *
174      * Following xml format is expected from the input stream.
175      *
176      *     <licenses>
177      *     <file-name contentId="content_id_of_license1">file1</file-name>
178      *     <file-name contentId="content_id_of_license2">file2</file-name>
179      *     ...
180      *     <file-content contentId="content_id_of_license1">license1 file contents</file-content>
181      *     <file-content contentId="content_id_of_license2">license2 file contents</file-content>
182      *     ...
183      *     </licenses>
184      */
185     @VisibleForTesting
parse(InputStreamReader in, Map<String, String> outFileNameToContentIdMap, Map<String, String> outContentIdToFileContentMap)186     static void parse(InputStreamReader in, Map<String, String> outFileNameToContentIdMap,
187             Map<String, String> outContentIdToFileContentMap)
188                     throws XmlPullParserException, IOException {
189         Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
190         Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
191 
192         XmlPullParser parser = Xml.newPullParser();
193         parser.setInput(in);
194         parser.nextTag();
195 
196         parser.require(XmlPullParser.START_TAG, "", TAG_ROOT);
197 
198         int state = parser.getEventType();
199         while (state != XmlPullParser.END_DOCUMENT) {
200             if (state == XmlPullParser.START_TAG) {
201                 if (TAG_FILE_NAME.equals(parser.getName())) {
202                     String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID);
203                         if (!TextUtils.isEmpty(contentId)) {
204                         String fileName = readText(parser).trim();
205                         if (!TextUtils.isEmpty(fileName)) {
206                             fileNameToContentIdMap.put(fileName, contentId);
207                         }
208                     }
209                 } else if (TAG_FILE_CONTENT.equals(parser.getName())) {
210                     String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID);
211                     if (!TextUtils.isEmpty(contentId) &&
212                             !outContentIdToFileContentMap.containsKey(contentId) &&
213                             !contentIdToFileContentMap.containsKey(contentId)) {
214                         String fileContent = readText(parser);
215                         if (!TextUtils.isEmpty(fileContent)) {
216                             contentIdToFileContentMap.put(contentId, fileContent);
217                         }
218                     }
219                 }
220             }
221 
222             state = parser.next();
223         }
224         outFileNameToContentIdMap.putAll(fileNameToContentIdMap);
225         outContentIdToFileContentMap.putAll(contentIdToFileContentMap);
226     }
227 
readText(XmlPullParser parser)228     private static String readText(XmlPullParser parser)
229             throws IOException, XmlPullParserException {
230         StringBuffer result = new StringBuffer();
231         int state = parser.next();
232         while (state == XmlPullParser.TEXT) {
233             result.append(parser.getText());
234             state = parser.next();
235         }
236         return result.toString();
237     }
238 
239     @VisibleForTesting
generateHtml(Map<String, String> fileNameToContentIdMap, Map<String, String> contentIdToFileContentMap, PrintWriter writer)240     static void generateHtml(Map<String, String> fileNameToContentIdMap,
241             Map<String, String> contentIdToFileContentMap, PrintWriter writer) {
242         List<String> fileNameList = new ArrayList();
243         fileNameList.addAll(fileNameToContentIdMap.keySet());
244         Collections.sort(fileNameList);
245 
246         writer.println(HTML_HEAD_STRING);
247 
248         int count = 0;
249         Map<String, Integer> contentIdToOrderMap = new HashMap();
250         List<ContentIdAndFileNames> contentIdAndFileNamesList = new ArrayList();
251 
252         // Prints all the file list with a link to its license file content.
253         for (String fileName : fileNameList) {
254             String contentId = fileNameToContentIdMap.get(fileName);
255             // Assigns an id to a newly referred license file content.
256             if (!contentIdToOrderMap.containsKey(contentId)) {
257                 contentIdToOrderMap.put(contentId, count);
258 
259                 // An index in contentIdAndFileNamesList is the order of each element.
260                 contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
261                 count++;
262             }
263 
264             int id = contentIdToOrderMap.get(contentId);
265             contentIdAndFileNamesList.get(id).mFileNameList.add(fileName);
266             writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
267         }
268 
269         writer.println(HTML_MIDDLE_STRING);
270 
271         count = 0;
272         // Prints all contents of the license files in order of id.
273         for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) {
274             writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", count);
275             writer.println("<div class=\"label\">Notices for file(s):</div>");
276             writer.println("<div class=\"file-list\">");
277             for (String fileName : contentIdAndFileNames.mFileNameList) {
278                 writer.format("%s <br/>\n", fileName);
279             }
280             writer.println("</div><!-- file-list -->");
281             writer.println("<pre class=\"license-text\">");
282             writer.println(contentIdToFileContentMap.get(
283                     contentIdAndFileNames.mContentId));
284             writer.println("</pre><!-- license-text -->");
285             writer.println("</td></tr><!-- same-license -->");
286 
287             count++;
288         }
289 
290         writer.println(HTML_REAR_STRING);
291     }
292 }
293