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); + } +}