| /* |
| * Copyright (C) 2017 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.android.settings; |
| |
| import android.support.annotation.VisibleForTesting; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.util.Xml; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.zip.GZIPInputStream; |
| |
| /** |
| * The utility class that generate a license html file from xml files. |
| * All the HTML snippets and logic are copied from build/make/tools/generate-notice-files.py. |
| * |
| * TODO: Remove duplicate codes once backward support ends. |
| */ |
| class LicenseHtmlGeneratorFromXml { |
| private static final String TAG = "LicenseHtmlGeneratorFromXml"; |
| |
| private static final String TAG_ROOT = "licenses"; |
| private static final String TAG_FILE_NAME = "file-name"; |
| private static final String TAG_FILE_CONTENT = "file-content"; |
| private static final String ATTR_CONTENT_ID = "contentId"; |
| |
| private static final String HTML_HEAD_STRING = |
| "<html><head>\n" + |
| "<style type=\"text/css\">\n" + |
| "body { padding: 0; font-family: sans-serif; }\n" + |
| ".same-license { background-color: #eeeeee;\n" + |
| " border-top: 20px solid white;\n" + |
| " padding: 10px; }\n" + |
| ".label { font-weight: bold; }\n" + |
| ".file-list { margin-left: 1em; color: blue; }\n" + |
| "</style>\n" + |
| "</head>" + |
| "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n" + |
| "<div class=\"toc\">\n" + |
| "<ul>"; |
| |
| private static final String HTML_MIDDLE_STRING = |
| "</ul>\n" + |
| "</div><!-- table of contents -->\n" + |
| "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">"; |
| |
| private static final String HTML_REAR_STRING = |
| "</table></body></html>"; |
| |
| private final List<File> mXmlFiles; |
| |
| /* |
| * A map from a file name to a content id (MD5 sum of file content) for its license. |
| * For example, "/system/priv-app/TeleService/TeleService.apk" maps to |
| * "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum |
| * of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2. |
| */ |
| private final Map<String, String> mFileNameToContentIdMap = new HashMap(); |
| |
| /* |
| * A map from a content id (MD5 sum of file content) to a license file content. |
| * For example, "9645f39e9db895a4aa6e02cb57294595" maps to the content string of |
| * packages/services/Telephony/MODULE_LICENSE_APACHE2. Here "9645f39e9db895a4aa6e02cb57294595" |
| * is a MD5 sum of the file content. |
| */ |
| private final Map<String, String> mContentIdToFileContentMap = new HashMap(); |
| |
| static class ContentIdAndFileNames { |
| final String mContentId; |
| final List<String> mFileNameList = new ArrayList(); |
| |
| ContentIdAndFileNames(String contentId) { |
| mContentId = contentId; |
| } |
| } |
| |
| private LicenseHtmlGeneratorFromXml(List<File> xmlFiles) { |
| mXmlFiles = xmlFiles; |
| } |
| |
| public static boolean generateHtml(List<File> xmlFiles, File outputFile) { |
| LicenseHtmlGeneratorFromXml genertor = new LicenseHtmlGeneratorFromXml(xmlFiles); |
| return genertor.generateHtml(outputFile); |
| } |
| |
| private boolean generateHtml(File outputFile) { |
| for (File xmlFile : mXmlFiles) { |
| parse(xmlFile); |
| } |
| |
| if (mFileNameToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) { |
| return false; |
| } |
| |
| PrintWriter writer = null; |
| try { |
| writer = new PrintWriter(outputFile); |
| |
| generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer); |
| |
| writer.flush(); |
| writer.close(); |
| return true; |
| } catch (FileNotFoundException | SecurityException e) { |
| Log.e(TAG, "Failed to generate " + outputFile, e); |
| |
| if (writer != null) { |
| writer.close(); |
| } |
| return false; |
| } |
| } |
| |
| private void parse(File xmlFile) { |
| if (xmlFile == null || !xmlFile.exists() || xmlFile.length() == 0) { |
| return; |
| } |
| |
| InputStreamReader in = null; |
| try { |
| if (xmlFile.getName().endsWith(".gz")) { |
| in = new InputStreamReader(new GZIPInputStream(new FileInputStream(xmlFile))); |
| } else { |
| in = new FileReader(xmlFile); |
| } |
| |
| parse(in, mFileNameToContentIdMap, mContentIdToFileContentMap); |
| |
| in.close(); |
| } catch (XmlPullParserException | IOException e) { |
| Log.e(TAG, "Failed to parse " + xmlFile, e); |
| if (in != null) { |
| try { |
| in.close(); |
| } catch (IOException ie) { |
| Log.w(TAG, "Failed to close " + xmlFile); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Parses an input stream and fills a map from a file name to a content id for its license |
| * and a map from a content id to a license file content. |
| * |
| * Following xml format is expected from the input stream. |
| * |
| * <licenses> |
| * <file-name contentId="content_id_of_license1">file1</file-name> |
| * <file-name contentId="content_id_of_license2">file2</file-name> |
| * ... |
| * <file-content contentId="content_id_of_license1">license1 file contents</file-content> |
| * <file-content contentId="content_id_of_license2">license2 file contents</file-content> |
| * ... |
| * </licenses> |
| */ |
| @VisibleForTesting |
| static void parse(InputStreamReader in, Map<String, String> outFileNameToContentIdMap, |
| Map<String, String> outContentIdToFileContentMap) |
| throws XmlPullParserException, IOException { |
| Map<String, String> fileNameToContentIdMap = new HashMap<String, String>(); |
| Map<String, String> contentIdToFileContentMap = new HashMap<String, String>(); |
| |
| XmlPullParser parser = Xml.newPullParser(); |
| parser.setInput(in); |
| parser.nextTag(); |
| |
| parser.require(XmlPullParser.START_TAG, "", TAG_ROOT); |
| |
| int state = parser.getEventType(); |
| while (state != XmlPullParser.END_DOCUMENT) { |
| if (state == XmlPullParser.START_TAG) { |
| if (TAG_FILE_NAME.equals(parser.getName())) { |
| String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID); |
| if (!TextUtils.isEmpty(contentId)) { |
| String fileName = readText(parser).trim(); |
| if (!TextUtils.isEmpty(fileName)) { |
| fileNameToContentIdMap.put(fileName, contentId); |
| } |
| } |
| } else if (TAG_FILE_CONTENT.equals(parser.getName())) { |
| String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID); |
| if (!TextUtils.isEmpty(contentId) && |
| !outContentIdToFileContentMap.containsKey(contentId) && |
| !contentIdToFileContentMap.containsKey(contentId)) { |
| String fileContent = readText(parser); |
| if (!TextUtils.isEmpty(fileContent)) { |
| contentIdToFileContentMap.put(contentId, fileContent); |
| } |
| } |
| } |
| } |
| |
| state = parser.next(); |
| } |
| outFileNameToContentIdMap.putAll(fileNameToContentIdMap); |
| outContentIdToFileContentMap.putAll(contentIdToFileContentMap); |
| } |
| |
| private static String readText(XmlPullParser parser) |
| throws IOException, XmlPullParserException { |
| StringBuffer result = new StringBuffer(); |
| int state = parser.next(); |
| while (state == XmlPullParser.TEXT) { |
| result.append(parser.getText()); |
| state = parser.next(); |
| } |
| return result.toString(); |
| } |
| |
| @VisibleForTesting |
| static void generateHtml(Map<String, String> fileNameToContentIdMap, |
| Map<String, String> contentIdToFileContentMap, PrintWriter writer) { |
| List<String> fileNameList = new ArrayList(); |
| fileNameList.addAll(fileNameToContentIdMap.keySet()); |
| Collections.sort(fileNameList); |
| |
| writer.println(HTML_HEAD_STRING); |
| |
| int count = 0; |
| Map<String, Integer> contentIdToOrderMap = new HashMap(); |
| List<ContentIdAndFileNames> contentIdAndFileNamesList = new ArrayList(); |
| |
| // Prints all the file list with a link to its license file content. |
| for (String fileName : fileNameList) { |
| String contentId = fileNameToContentIdMap.get(fileName); |
| // Assigns an id to a newly referred license file content. |
| if (!contentIdToOrderMap.containsKey(contentId)) { |
| contentIdToOrderMap.put(contentId, count); |
| |
| // An index in contentIdAndFileNamesList is the order of each element. |
| contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId)); |
| count++; |
| } |
| |
| int id = contentIdToOrderMap.get(contentId); |
| contentIdAndFileNamesList.get(id).mFileNameList.add(fileName); |
| writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName); |
| } |
| |
| writer.println(HTML_MIDDLE_STRING); |
| |
| count = 0; |
| // Prints all contents of the license files in order of id. |
| for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) { |
| writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", count); |
| writer.println("<div class=\"label\">Notices for file(s):</div>"); |
| writer.println("<div class=\"file-list\">"); |
| for (String fileName : contentIdAndFileNames.mFileNameList) { |
| writer.format("%s <br/>\n", fileName); |
| } |
| writer.println("</div><!-- file-list -->"); |
| writer.println("<pre class=\"license-text\">"); |
| writer.println(contentIdToFileContentMap.get( |
| contentIdAndFileNames.mContentId)); |
| writer.println("</pre><!-- license-text -->"); |
| writer.println("</td></tr><!-- same-license -->"); |
| |
| count++; |
| } |
| |
| writer.println(HTML_REAR_STRING); |
| } |
| } |