From 74812873f41adbd34ab3ff4fbd7dcbb8f6fde233 Mon Sep 17 00:00:00 2001 From: Jaekyun Seok Date: Tue, 18 Apr 2017 15:22:01 +0900 Subject: [PATCH] Generate license html file from xml files of partitions Treble-ization requires each partner to store their license information into their own partition because each partition can be updated individually. So each partition will have its own NOTICE.xml.gz, and Settings should be able to generate license html from xml files of partitions. Test: building succeeded and tested on sailfish. make ROBOTEST_FILTER=LicenseHtmlGeneratorFromXmlTest RunSettingsRoboTests make ROBOTEST_FILTER=LicenseHtmlLoaderTest RunSettingsRoboTests make ROBOTEST_FILTER=SettingsLicenseActivityTest RunSettingsRoboTests Bug: 37099941 Change-Id: If797759d300ee20dd43ad8efd7d17b4f7e0c4537 --- .../settings/LicenseHtmlGeneratorFromXml.java | 292 ++++++++++++++++++ .../android/settings/LicenseHtmlLoader.java | 110 +++++++ .../settings/SettingsLicenseActivity.java | 78 ++++- .../LicenseHtmlGeneratorFromXmlTest.java | 123 ++++++++ .../settings/LicenseHtmlLoaderTest.java | 109 +++++++ .../settings/SettingsLicenseActivityTest.java | 116 +++++++ 6 files changed, 824 insertions(+), 4 deletions(-) create mode 100644 src/com/android/settings/LicenseHtmlGeneratorFromXml.java create mode 100644 src/com/android/settings/LicenseHtmlLoader.java create mode 100644 tests/robotests/src/com/android/settings/LicenseHtmlGeneratorFromXmlTest.java create mode 100644 tests/robotests/src/com/android/settings/LicenseHtmlLoaderTest.java create mode 100644 tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java diff --git a/src/com/android/settings/LicenseHtmlGeneratorFromXml.java b/src/com/android/settings/LicenseHtmlGeneratorFromXml.java new file mode 100644 index 00000000000..7025c5adf3f --- /dev/null +++ b/src/com/android/settings/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.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 = + "\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/src/com/android/settings/LicenseHtmlLoader.java b/src/com/android/settings/LicenseHtmlLoader.java new file mode 100644 index 00000000000..97179269013 --- /dev/null +++ b/src/com/android/settings/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.settings; + +import android.content.Context; +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import com.android.settings.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. + */ +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/src/com/android/settings/SettingsLicenseActivity.java b/src/com/android/settings/SettingsLicenseActivity.java index e0b7efe5c60..5b23a68990a 100644 --- a/src/com/android/settings/SettingsLicenseActivity.java +++ b/src/com/android/settings/SettingsLicenseActivity.java @@ -17,32 +17,87 @@ package com.android.settings; import android.app.Activity; +import android.app.LoaderManager; import android.content.ActivityNotFoundException; +import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; +import android.content.Loader; import android.net.Uri; import android.os.Bundle; import android.os.StrictMode; import android.os.SystemProperties; +import android.support.annotation.VisibleForTesting; +import android.support.v4.content.FileProvider; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; +import com.android.settings.users.RestrictedProfileSettings; + import java.io.File; +import java.util.ArrayList; +import java.util.List; /** * The "dialog" that shows from "License" in the Settings app. */ -public class SettingsLicenseActivity extends Activity { +public class SettingsLicenseActivity extends Activity implements + LoaderManager.LoaderCallbacks { private static final String TAG = "SettingsLicenseActivity"; private static final String DEFAULT_LICENSE_PATH = "/system/etc/NOTICE.html.gz"; private static final String PROPERTY_LICENSE_PATH = "ro.config.license_path"; + private static final int LOADER_ID_LICENSE_HTML_LOADER = 0; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final String path = SystemProperties.get(PROPERTY_LICENSE_PATH, DEFAULT_LICENSE_PATH); + final String licenseHtmlPath = + SystemProperties.get(PROPERTY_LICENSE_PATH, DEFAULT_LICENSE_PATH); + if (isFilePathValid(licenseHtmlPath)) { + showSelectedFile(licenseHtmlPath); + } else { + showHtmlFromDefaultXmlFiles(); + } + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new LicenseHtmlLoader(this); + } + + @Override + public void onLoadFinished(Loader loader, File generatedHtmlFile) { + showGeneratedHtmlFile(generatedHtmlFile); + } + + @Override + public void onLoaderReset(Loader loader) { + } + + private void showHtmlFromDefaultXmlFiles() { + getLoaderManager().initLoader(LOADER_ID_LICENSE_HTML_LOADER, Bundle.EMPTY, this); + } + + @VisibleForTesting + Uri getUriFromGeneratedHtmlFile(File generatedHtmlFile) { + return FileProvider.getUriForFile(this, RestrictedProfileSettings.FILE_PROVIDER_AUTHORITY, + generatedHtmlFile); + } + + private void showGeneratedHtmlFile(File generatedHtmlFile) { + if (generatedHtmlFile != null) { + showHtmlFromUri(getUriFromGeneratedHtmlFile(generatedHtmlFile)); + } else { + Log.e(TAG, "Failed to generate."); + showErrorAndFinish(); + } + } + + private void showSelectedFile(final String path) { if (TextUtils.isEmpty(path)) { Log.e(TAG, "The system property for the license file is empty"); showErrorAndFinish(); @@ -50,18 +105,24 @@ public class SettingsLicenseActivity extends Activity { } final File file = new File(path); - if (!file.exists() || file.length() == 0) { + if (!isFileValid(file)) { Log.e(TAG, "License file " + path + " does not exist"); showErrorAndFinish(); return; } + showHtmlFromUri(Uri.fromFile(file)); + } + private void showHtmlFromUri(Uri uri) { // Kick off external viewer due to WebView security restrictions; we // carefully point it at HTMLViewer, since it offers to decompress // before viewing. final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.fromFile(file), "text/html"); + intent.setDataAndType(uri, "text/html"); intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.settings_license_activity_title)); + if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setPackage("com.android.htmlviewer"); @@ -79,4 +140,13 @@ public class SettingsLicenseActivity extends Activity { .show(); finish(); } + + private boolean isFilePathValid(final String path) { + return !TextUtils.isEmpty(path) && isFileValid(new File(path)); + } + + @VisibleForTesting + boolean isFileValid(final File file) { + return file.exists() && file.length() != 0; + } } diff --git a/tests/robotests/src/com/android/settings/LicenseHtmlGeneratorFromXmlTest.java b/tests/robotests/src/com/android/settings/LicenseHtmlGeneratorFromXmlTest.java new file mode 100644 index 00000000000..ef36e5f5f06 --- /dev/null +++ b/tests/robotests/src/com/android/settings/LicenseHtmlGeneratorFromXmlTest.java @@ -0,0 +1,123 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import java.util.HashMap; +import java.util.Map; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; +import org.xmlpull.v1.XmlPullParserException; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class LicenseHtmlGeneratorFromXmlTest { + private static final String VAILD_XML_STRING = + "\n" + + "\n" + + "/file0\n" + + "/file1\n" + + "\n" + + ""; + + private static final String INVAILD_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(VAILD_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(INVAILD_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/tests/robotests/src/com/android/settings/LicenseHtmlLoaderTest.java b/tests/robotests/src/com/android/settings/LicenseHtmlLoaderTest.java new file mode 100644 index 00000000000..96e88c23395 --- /dev/null +++ b/tests/robotests/src/com/android/settings/LicenseHtmlLoaderTest.java @@ -0,0 +1,109 @@ +/* + * 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 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 static org.mockito.Mockito.when; + +import android.content.Context; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +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; + +@RunWith(SettingsRobolectricTestRunner.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/tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java b/tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java new file mode 100644 index 00000000000..3e28a2a9d54 --- /dev/null +++ b/tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java @@ -0,0 +1,116 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.app.Application; +import android.os.Bundle; +import android.os.SystemProperties; +import android.content.Context; +import android.content.Intent; +import android.content.Loader; +import android.net.Uri; +import android.os.Bundle; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.res.builder.RobolectricPackageManager; +import org.robolectric.util.ActivityController; +import org.robolectric.shadows.ShadowActivity; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SettingsLicenseActivityTest { + private ActivityController mActivityController; + private SettingsLicenseActivity mActivity; + private Application mApplication; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mApplication = RuntimeEnvironment.application; + mActivityController = Robolectric.buildActivity(SettingsLicenseActivity.class); + mActivity = spy(mActivityController.get()); + } + + void assertEqualIntents(Intent actual, Intent expected) { + assertThat(actual.getAction()).isEqualTo(expected.getAction()); + assertThat(actual.getDataString()).isEqualTo(expected.getDataString()); + assertThat(actual.getType()).isEqualTo(expected.getType()); + assertThat(actual.getCategories()).isEqualTo(expected.getCategories()); + assertThat(actual.getPackage()).isEqualTo(expected.getPackage()); + assertThat(actual.getFlags()).isEqualTo(expected.getFlags()); + } + + @Test + public void testOnCreateWithValidHtmlFile() { + SystemProperties.set("ro.config.license_path", "/system/etc/NOTICE.html.gz"); + + doReturn(true).when(mActivity).isFileValid(any()); + mActivity.onCreate(null); + + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse("file:///system/etc/NOTICE.html.gz"), "text/html"); + intent.putExtra(Intent.EXTRA_TITLE, mActivity.getString( + R.string.settings_license_activity_title)); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setPackage("com.android.htmlviewer"); + + assertEqualIntents(shadowOf(mApplication).getNextStartedActivity(), intent); + } + + @Test + public void testOnCreateWithGeneratedHtmlFile() { + doReturn(null).when(mActivity).onCreateLoader(anyInt(), any()); + doReturn(Uri.parse("content://com.android.settings.files/my_cache/generated_test.html")) + .when(mActivity).getUriFromGeneratedHtmlFile(any()); + + mActivity.onCreate(null); + mActivity.onLoadFinished(null, new File("/generated_test.html")); + + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType( + Uri.parse("content://com.android.settings.files/my_cache/generated_test.html"), + "text/html"); + intent.putExtra(Intent.EXTRA_TITLE, mActivity.getString( + R.string.settings_license_activity_title)); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setPackage("com.android.htmlviewer"); + + assertEqualIntents(shadowOf(mApplication).getNextStartedActivity(), intent); + } +}