From 8d923f0b8eddb119b5f928f0a626a5b4154220a2 Mon Sep 17 00:00:00 2001 From: Jaekyun Seok Date: Fri, 1 Dec 2017 10:37:10 +0900 Subject: [PATCH] Move codes generating html file from xml files to SettingsLib (2/2) LicenseHtmlGeneratorFromXml, LicenseHtmlLoader and indirecly AsyncLoader shoud be commonly used by Settings and TvSettings. So this CL will move them to SettingsLib. Bug: 37763148 Test: building succeeded and tested on sailfish make ROBOTEST_FILTER=LicenseHtmlGeneratorFromXmlTest RunSettingsLibRoboTests make ROBOTEST_FILTER=LicenseHtmlLoaderTest RunSettingsLibRoboTests Change-Id: I86b59d82c46b6c5f520a6be260363f1b6ffd43f4 --- .../license/LicenseHtmlGeneratorFromXml.java | 292 ++++++++++++++++++ .../license/LicenseHtmlLoader.java | 110 +++++++ .../settingslib/utils/AsyncLoader.java | 110 +++++++ .../LicenseHtmlGeneratorFromXmlTest.java | 126 ++++++++ .../license/LicenseHtmlLoaderTest.java | 110 +++++++ .../server/am/ActivityManagerService.java | 5 +- 6 files changed, 752 insertions(+), 1 deletion(-) create mode 100644 packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java create mode 100644 packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java create mode 100644 packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderTest.java diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java new file mode 100644 index 0000000000000..e3e27ce17c9a8 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java @@ -0,0 +1,292 @@ +/* + * 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.settingslib.license; + +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 = + "\n" + + "\n" + + "" + + "\n" + + "
\n" + + "\n" + + "
\n" + + ""; + + private static final String HTML_REAR_STRING = + "
"; + + private final List 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 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 mContentIdToFileContentMap = new HashMap(); + + static class ContentIdAndFileNames { + final String mContentId; + final List mFileNameList = new ArrayList(); + + ContentIdAndFileNames(String contentId) { + mContentId = contentId; + } + } + + private LicenseHtmlGeneratorFromXml(List xmlFiles) { + mXmlFiles = xmlFiles; + } + + public static boolean generateHtml(List 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. + * + * + * file1 + * file2 + * ... + * license1 file contents + * license2 file contents + * ... + * + */ + @VisibleForTesting + static void parse(InputStreamReader in, Map outFileNameToContentIdMap, + Map outContentIdToFileContentMap) + throws XmlPullParserException, IOException { + Map fileNameToContentIdMap = new HashMap(); + Map contentIdToFileContentMap = new HashMap(); + + 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 fileNameToContentIdMap, + Map contentIdToFileContentMap, PrintWriter writer) { + List fileNameList = new ArrayList(); + fileNameList.addAll(fileNameToContentIdMap.keySet()); + Collections.sort(fileNameList); + + writer.println(HTML_HEAD_STRING); + + int count = 0; + Map contentIdToOrderMap = new HashMap(); + List 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("
  • %s
  • \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("\n", count); + writer.println("
    Notices for file(s):
    "); + writer.println("
    "); + for (String fileName : contentIdAndFileNames.mFileNameList) { + writer.format("%s
    \n", fileName); + } + writer.println("
    "); + writer.println("
    ");
    +            writer.println(contentIdToFileContentMap.get(
    +                    contentIdAndFileNames.mContentId));
    +            writer.println("
    "); + writer.println(""); + + count++; + } + + writer.println(HTML_REAR_STRING); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java new file mode 100644 index 0000000000000..a9fb20ca9e170 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java @@ -0,0 +1,110 @@ +/* + * 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.settingslib.license; + +import android.content.Context; +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import com.android.settingslib.utils.AsyncLoader; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * LicenseHtmlLoader is a loader which loads a license html file from default license xml files. + */ +public class LicenseHtmlLoader extends AsyncLoader { + private static final String TAG = "LicenseHtmlLoader"; + + private static final String[] DEFAULT_LICENSE_XML_PATHS = { + "/system/etc/NOTICE.xml.gz", + "/vendor/etc/NOTICE.xml.gz", + "/odm/etc/NOTICE.xml.gz", + "/oem/etc/NOTICE.xml.gz"}; + private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html"; + + private Context mContext; + + public LicenseHtmlLoader(Context context) { + super(context); + mContext = context; + } + + @Override + public File loadInBackground() { + return generateHtmlFromDefaultXmlFiles(); + } + + @Override + protected void onDiscardResult(File f) { + } + + private File generateHtmlFromDefaultXmlFiles() { + final List xmlFiles = getVaildXmlFiles(); + if (xmlFiles.isEmpty()) { + Log.e(TAG, "No notice file exists."); + return null; + } + + File cachedHtmlFile = getCachedHtmlFile(); + if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile) + || generateHtmlFile(xmlFiles, cachedHtmlFile)) { + return cachedHtmlFile; + } + + return null; + } + + @VisibleForTesting + List getVaildXmlFiles() { + final List xmlFiles = new ArrayList(); + for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) { + File file = new File(xmlPath); + if (file.exists() && file.length() != 0) { + xmlFiles.add(file); + } + } + return xmlFiles; + } + + @VisibleForTesting + File getCachedHtmlFile() { + return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME); + } + + @VisibleForTesting + boolean isCachedHtmlFileOutdated(List xmlFiles, File cachedHtmlFile) { + boolean outdated = true; + if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) { + outdated = false; + for (File file : xmlFiles) { + if (cachedHtmlFile.lastModified() < file.lastModified()) { + outdated = true; + break; + } + } + } + return outdated; + } + + @VisibleForTesting + boolean generateHtmlFile(List xmlFiles, File htmlFile) { + return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java new file mode 100644 index 0000000000000..06770ac095586 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java @@ -0,0 +1,110 @@ +/* + * 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.settingslib.utils; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +/** + * This class fills in some boilerplate for AsyncTaskLoader to actually load things. + * + * Subclasses need to implement {@link AsyncLoader#loadInBackground()} to perform the actual + * background task, and {@link AsyncLoader#onDiscardResult(T)} to clean up previously loaded + * results. + * + * This loader is based on the MailAsyncTaskLoader from the AOSP EmailUnified repo. + * + * @param the data type to be loaded. + */ +public abstract class AsyncLoader extends AsyncTaskLoader { + private T mResult; + + public AsyncLoader(final Context context) { + super(context); + } + + @Override + protected void onStartLoading() { + if (mResult != null) { + deliverResult(mResult); + } + + if (takeContentChanged() || mResult == null) { + forceLoad(); + } + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + public void deliverResult(final T data) { + if (isReset()) { + if (data != null) { + onDiscardResult(data); + } + return; + } + + final T oldResult = mResult; + mResult = data; + + if (isStarted()) { + super.deliverResult(data); + } + + if (oldResult != null && oldResult != mResult) { + onDiscardResult(oldResult); + } + } + + @Override + protected void onReset() { + super.onReset(); + + onStopLoading(); + + if (mResult != null) { + onDiscardResult(mResult); + } + mResult = null; + } + + @Override + public void onCanceled(final T data) { + super.onCanceled(data); + + if (data != null) { + onDiscardResult(data); + } + } + + /** + * Called when discarding the load results so subclasses can take care of clean-up or + * recycling tasks. This is not called if the same result (by way of pointer equality) is + * returned again by a subsequent call to loadInBackground, or if result is null. + * + * Note that this may be called concurrently with loadInBackground(), and in some circumstances + * may be called more than once for a given object. + * + * @param result The value returned from {@link AsyncLoader#loadInBackground()} which + * is to be discarded. + */ + protected abstract void onDiscardResult(T result); +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java new file mode 100644 index 0000000000000..c7e9262ec4d80 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java @@ -0,0 +1,126 @@ +/* + * 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.settingslib.license; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.settingslib.TestConfig; +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class LicenseHtmlGeneratorFromXmlTest { + private static final String VALILD_XML_STRING = + "\n" + + "\n" + + "/file0\n" + + "/file1\n" + + "\n" + + ""; + + private static final String INVALILD_XML_STRING = + "\n" + + "\n" + + "/file0\n" + + "/file1\n" + + "\n" + + ""; + + private static final String EXPECTED_HTML_STRING = + "\n" + + "\n" + + "" + + "\n" + + "
    \n" + + "\n" + + "
    \n" + + "\n" + + "\n" + + "
    \n" + + "
    Notices for file(s):
    \n" + + "
    \n" + + "/file0
    \n" + + "/file1
    \n" + + "
    \n" + + "
    \n"
    +            + "license content #0\n"
    +            + "
    \n" + + "
    \n"; + + @Test + public void testParseValidXmlStream() throws XmlPullParserException, IOException { + Map fileNameToContentIdMap = new HashMap(); + Map contentIdToFileContentMap = new HashMap(); + + LicenseHtmlGeneratorFromXml.parse( + new InputStreamReader(new ByteArrayInputStream(VALILD_XML_STRING.getBytes())), + fileNameToContentIdMap, contentIdToFileContentMap); + assertThat(fileNameToContentIdMap.size()).isEqualTo(2); + assertThat(fileNameToContentIdMap.get("/file0")).isEqualTo("0"); + assertThat(fileNameToContentIdMap.get("/file1")).isEqualTo("0"); + assertThat(contentIdToFileContentMap.size()).isEqualTo(1); + assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0"); + } + + @Test(expected = XmlPullParserException.class) + public void testParseInvalidXmlStream() throws XmlPullParserException, IOException { + Map fileNameToContentIdMap = new HashMap(); + Map contentIdToFileContentMap = new HashMap(); + + LicenseHtmlGeneratorFromXml.parse( + new InputStreamReader(new ByteArrayInputStream(INVALILD_XML_STRING.getBytes())), + fileNameToContentIdMap, contentIdToFileContentMap); + } + + @Test + public void testGenerateHtml() { + Map fileNameToContentIdMap = new HashMap(); + Map contentIdToFileContentMap = new HashMap(); + + fileNameToContentIdMap.put("/file0", "0"); + fileNameToContentIdMap.put("/file1", "0"); + contentIdToFileContentMap.put("0", "license content #0"); + + StringWriter output = new StringWriter(); + LicenseHtmlGeneratorFromXml.generateHtml( + fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output)); + assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderTest.java new file mode 100644 index 0000000000000..1a6f30c49e462 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderTest.java @@ -0,0 +1,110 @@ +/* + * 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.settingslib.license; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; + +import com.android.settingslib.TestConfig; +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +import java.io.File; +import java.util.ArrayList; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class LicenseHtmlLoaderTest { + @Mock + private Context mContext; + + LicenseHtmlLoader newLicenseHtmlLoader(ArrayList xmlFiles, + File cachedHtmlFile, boolean isCachedHtmlFileOutdated, + boolean generateHtmlFileSucceeded) { + LicenseHtmlLoader loader = spy(new LicenseHtmlLoader(mContext)); + doReturn(xmlFiles).when(loader).getVaildXmlFiles(); + doReturn(cachedHtmlFile).when(loader).getCachedHtmlFile(); + doReturn(isCachedHtmlFileOutdated).when(loader).isCachedHtmlFileOutdated(any(), any()); + doReturn(generateHtmlFileSucceeded).when(loader).generateHtmlFile(any(), any()); + return loader; + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testLoadInBackground() { + ArrayList xmlFiles = new ArrayList(); + xmlFiles.add(new File("test.xml")); + File cachedHtmlFile = new File("test.html"); + + LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true); + + assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile); + verify(loader).generateHtmlFile(any(), any()); + } + + @Test + public void testLoadInBackgroundWithNoVaildXmlFiles() { + ArrayList xmlFiles = new ArrayList(); + File cachedHtmlFile = new File("test.html"); + + LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true); + + assertThat(loader.loadInBackground()).isNull(); + verify(loader, never()).generateHtmlFile(any(), any()); + } + + @Test + public void testLoadInBackgroundWithNonOutdatedCachedHtmlFile() { + ArrayList xmlFiles = new ArrayList(); + xmlFiles.add(new File("test.xml")); + File cachedHtmlFile = new File("test.html"); + + LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, false, true); + + assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile); + verify(loader, never()).generateHtmlFile(any(), any()); + } + + @Test + public void testLoadInBackgroundWithGenerateHtmlFileFailed() { + ArrayList xmlFiles = new ArrayList(); + xmlFiles.add(new File("test.xml")); + File cachedHtmlFile = new File("test.html"); + + LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, false); + + assertThat(loader.loadInBackground()).isNull(); + verify(loader).generateHtmlFile(any(), any()); + } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 399760d492d62..c8bc163619c0a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -8876,7 +8876,10 @@ public class ActivityManagerService extends IActivityManager.Stub final int callingAppId = UserHandle.getAppId(callingUid); if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) { if ("com.android.settings.files".equals(grantUri.uri.getAuthority())) { - // Exempted authority for cropping user photos in Settings app + // Exempted authority for + // 1. cropping user photos and sharing a generated license html + // file in Settings app + // 2. sharing a generated license html file in TvSettings app } else { Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission" + " grant to " + grantUri + "; use startActivityAsCaller() instead");