blob: 7025c5adf3fa99ecec3380349a86797cb88e5d20 [file] [log] [blame]
Jaekyun Seok74812872017-04-18 15:22:01 +09001/*
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
17package com.android.settings;
18
19import android.support.annotation.VisibleForTesting;
20import android.text.TextUtils;
21import android.util.Log;
22import android.util.Xml;
23
24import org.xmlpull.v1.XmlPullParser;
25import org.xmlpull.v1.XmlPullParserException;
26
27import java.io.File;
28import java.io.FileInputStream;
29import java.io.FileNotFoundException;
30import java.io.FileReader;
31import java.io.IOException;
32import java.io.InputStreamReader;
33import java.io.PrintWriter;
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.HashMap;
37import java.util.List;
38import java.util.Map;
39import 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 */
47class 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
100 ContentIdAndFileNames(String contentId) {
101 mContentId = contentId;
102 }
103 }
104
105 private LicenseHtmlGeneratorFromXml(List<File> xmlFiles) {
106 mXmlFiles = xmlFiles;
107 }
108
109 public static boolean generateHtml(List<File> xmlFiles, File outputFile) {
110 LicenseHtmlGeneratorFromXml genertor = new LicenseHtmlGeneratorFromXml(xmlFiles);
111 return genertor.generateHtml(outputFile);
112 }
113
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
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
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
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
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}