Merge "Update vpn icon from setting gear to vpn key." into oc-dev
diff --git a/src/com/android/settings/ApnEditor.java b/src/com/android/settings/ApnEditor.java
index f668957..d5affa8 100644
--- a/src/com/android/settings/ApnEditor.java
+++ b/src/com/android/settings/ApnEditor.java
@@ -64,6 +64,7 @@
implements OnPreferenceChangeListener, OnKeyListener {
private final static String TAG = ApnEditor.class.getSimpleName();
+ private final static boolean VDBG = false; // STOPSHIP if true
private final static String SAVED_POS = "pos";
private final static String KEY_AUTH_TYPE = "auth_type";
@@ -785,6 +786,46 @@
}
/**
+ * Add key, value to cv and compare the value against the value at index in mCursor. Return true
+ * if values are different. assumeDiff indicates if values can be assumed different in which
+ * case no comparison is needed.
+ * @return true if value is different from the value at index in mCursor
+ */
+ boolean setStringValueAndCheckIfDiff(ContentValues cv, String key, String value,
+ boolean assumeDiff, int index) {
+ cv.put(key, value);
+ String valueFromCursor = mCursor.getString(index);
+ if (VDBG) {
+ Log.d(TAG, "setStringValueAndCheckIfDiff: assumeDiff: " + assumeDiff
+ + " key: " + key
+ + " value: '" + value
+ + "' valueFromCursor: '" + valueFromCursor + "'");
+ }
+ return assumeDiff
+ || !((TextUtils.isEmpty(value) && TextUtils.isEmpty(valueFromCursor))
+ || (value != null && value.equals(valueFromCursor)));
+ }
+
+ /**
+ * Add key, value to cv and compare the value against the value at index in mCursor. Return true
+ * if values are different. assumeDiff indicates if values can be assumed different in which
+ * case no comparison is needed.
+ * @return true if value is different from the value at index in mCursor
+ */
+ boolean setIntValueAndCheckIfDiff(ContentValues cv, String key, int value,
+ boolean assumeDiff, int index) {
+ cv.put(key, value);
+ int valueFromCursor = mCursor.getInt(index);
+ if (VDBG) {
+ Log.d(TAG, "setIntValueAndCheckIfDiff: assumeDiff: " + assumeDiff
+ + " key: " + key
+ + " value: '" + value
+ + "' valueFromCursor: '" + valueFromCursor + "'");
+ }
+ return assumeDiff || value != valueFromCursor;
+ }
+
+ /**
* Check the key fields' validity and save if valid.
* @param force save even if the fields are not valid, if the app is
* being suspended
@@ -820,33 +861,110 @@
}
ContentValues values = new ContentValues();
+ // call update() if it's a new APN. If not, check if any field differs from the db value;
+ // if any diff is found update() should be called
+ boolean callUpdate = mNewApn;
// Add a dummy name "Untitled", if the user exits the screen without adding a name but
// entered other information worth keeping.
- values.put(Telephony.Carriers.NAME,
- name.length() < 1 ? getResources().getString(R.string.untitled_apn) : name);
- values.put(Telephony.Carriers.APN, apn);
- values.put(Telephony.Carriers.PROXY, checkNotSet(mProxy.getText()));
- values.put(Telephony.Carriers.PORT, checkNotSet(mPort.getText()));
- values.put(Telephony.Carriers.MMSPROXY, checkNotSet(mMmsProxy.getText()));
- values.put(Telephony.Carriers.MMSPORT, checkNotSet(mMmsPort.getText()));
- values.put(Telephony.Carriers.USER, checkNotSet(mUser.getText()));
- values.put(Telephony.Carriers.SERVER, checkNotSet(mServer.getText()));
- values.put(Telephony.Carriers.PASSWORD, checkNotSet(mPassword.getText()));
- values.put(Telephony.Carriers.MMSC, checkNotSet(mMmsc.getText()));
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.NAME,
+ name.length() < 1 ? getResources().getString(R.string.untitled_apn) : name,
+ callUpdate,
+ NAME_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.APN,
+ apn,
+ callUpdate,
+ APN_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.PROXY,
+ checkNotSet(mProxy.getText()),
+ callUpdate,
+ PROXY_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.PORT,
+ checkNotSet(mPort.getText()),
+ callUpdate,
+ PORT_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MMSPROXY,
+ checkNotSet(mMmsProxy.getText()),
+ callUpdate,
+ MMSPROXY_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MMSPORT,
+ checkNotSet(mMmsPort.getText()),
+ callUpdate,
+ MMSPORT_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.USER,
+ checkNotSet(mUser.getText()),
+ callUpdate,
+ USER_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.SERVER,
+ checkNotSet(mServer.getText()),
+ callUpdate,
+ SERVER_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.PASSWORD,
+ checkNotSet(mPassword.getText()),
+ callUpdate,
+ PASSWORD_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MMSC,
+ checkNotSet(mMmsc.getText()),
+ callUpdate,
+ MMSC_INDEX);
String authVal = mAuthType.getValue();
if (authVal != null) {
- values.put(Telephony.Carriers.AUTH_TYPE, Integer.parseInt(authVal));
+ callUpdate = setIntValueAndCheckIfDiff(values,
+ Telephony.Carriers.AUTH_TYPE,
+ Integer.parseInt(authVal),
+ callUpdate,
+ AUTH_TYPE_INDEX);
}
- values.put(Telephony.Carriers.PROTOCOL, checkNotSet(mProtocol.getValue()));
- values.put(Telephony.Carriers.ROAMING_PROTOCOL, checkNotSet(mRoamingProtocol.getValue()));
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.PROTOCOL,
+ checkNotSet(mProtocol.getValue()),
+ callUpdate,
+ PROTOCOL_INDEX);
- values.put(Telephony.Carriers.TYPE, checkNotSet(mApnType.getText()));
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.ROAMING_PROTOCOL,
+ checkNotSet(mRoamingProtocol.getValue()),
+ callUpdate,
+ ROAMING_PROTOCOL_INDEX);
- values.put(Telephony.Carriers.MCC, mcc);
- values.put(Telephony.Carriers.MNC, mnc);
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.TYPE,
+ checkNotSet(mApnType.getText()),
+ callUpdate,
+ TYPE_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MCC,
+ mcc,
+ callUpdate,
+ MCC_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MNC,
+ mnc,
+ callUpdate,
+ MNC_INDEX);
values.put(Telephony.Carriers.NUMERIC, mcc + mnc);
@@ -866,7 +984,11 @@
bearerBitmask |= ServiceState.getBitmaskForTech(Integer.parseInt(bearer));
}
}
- values.put(Telephony.Carriers.BEARER_BITMASK, bearerBitmask);
+ callUpdate = setIntValueAndCheckIfDiff(values,
+ Telephony.Carriers.BEARER_BITMASK,
+ bearerBitmask,
+ callUpdate,
+ BEARER_BITMASK_INDEX);
int bearerVal;
if (bearerBitmask == 0 || mBearerInitialVal == 0) {
@@ -879,13 +1001,35 @@
// random tech from the new bitmask??
bearerVal = 0;
}
- values.put(Telephony.Carriers.BEARER, bearerVal);
+ callUpdate = setIntValueAndCheckIfDiff(values,
+ Telephony.Carriers.BEARER,
+ bearerVal,
+ callUpdate,
+ BEARER_INDEX);
- values.put(Telephony.Carriers.MVNO_TYPE, checkNotSet(mMvnoType.getValue()));
- values.put(Telephony.Carriers.MVNO_MATCH_DATA, checkNotSet(mMvnoMatchData.getText()));
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MVNO_TYPE,
+ checkNotSet(mMvnoType.getValue()),
+ callUpdate,
+ MVNO_TYPE_INDEX);
- values.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled.isChecked() ? 1 : 0);
- getContentResolver().update(mUri, values, null, null);
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MVNO_MATCH_DATA,
+ checkNotSet(mMvnoMatchData.getText()),
+ callUpdate,
+ MVNO_MATCH_DATA_INDEX);
+
+ callUpdate = setIntValueAndCheckIfDiff(values,
+ Telephony.Carriers.CARRIER_ENABLED,
+ mCarrierEnabled.isChecked() ? 1 : 0,
+ callUpdate,
+ CARRIER_ENABLED_INDEX);
+
+ if (callUpdate) {
+ getContentResolver().update(mUri, values, null, null);
+ } else {
+ if (VDBG) Log.d(TAG, "validateAndSave: not calling update()");
+ }
return true;
}
diff --git a/src/com/android/settings/LicenseHtmlGeneratorFromXml.java b/src/com/android/settings/LicenseHtmlGeneratorFromXml.java
new file mode 100644
index 0000000..7025c5a
--- /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 =
+ "<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);
+ }
+}
diff --git a/src/com/android/settings/LicenseHtmlLoader.java b/src/com/android/settings/LicenseHtmlLoader.java
new file mode 100644
index 0000000..9717926
--- /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<File> {
+ 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<File> 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<File> getVaildXmlFiles() {
+ final List<File> 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<File> 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<File> 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 e0b7efe..5b23a68 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<File> {
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<File> onCreateLoader(int id, Bundle args) {
+ return new LicenseHtmlLoader(this);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<File> loader, File generatedHtmlFile) {
+ showGeneratedHtmlFile(generatedHtmlFile);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<File> 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 @@
}
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 @@
.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/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index 44dd353..eb3e726 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -478,7 +478,7 @@
final String serviceState = serviceEnabled ?
getString(R.string.accessibility_summary_state_enabled) :
getString(R.string.accessibility_summary_state_disabled);
- final String serviceSummary = info.loadSummary(getPackageManager());
+ final CharSequence serviceSummary = info.loadSummary(getPackageManager());
final String stateSummaryCombo = getString(
R.string.accessibility_summary_default_combination,
serviceState, serviceSummary);
diff --git a/src/com/android/settings/accessibility/ShortcutServicePickerFragment.java b/src/com/android/settings/accessibility/ShortcutServicePickerFragment.java
index 3250521..d7749ea 100644
--- a/src/com/android/settings/accessibility/ShortcutServicePickerFragment.java
+++ b/src/com/android/settings/accessibility/ShortcutServicePickerFragment.java
@@ -69,7 +69,7 @@
candidates.add(new DefaultAppInfo(mPm,
UserHandle.myUserId(),
installedServiceInfo.getComponentName(),
- installedServiceInfo.loadSummary(mPm.getPackageManager()),
+ (String) installedServiceInfo.loadSummary(mPm.getPackageManager()),
true /* enabled */));
}
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index f24c3f7..559204b 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -130,13 +130,13 @@
public static final int SIZE_EXTERNAL = 2;
// Filter options used for displayed list of applications
- // The order which they appear is the order they will show when spinner is present.
+ // Filters will appear sorted based on their value defined here.
public static final int FILTER_APPS_POWER_WHITELIST = 0;
public static final int FILTER_APPS_POWER_WHITELIST_ALL = 1;
public static final int FILTER_APPS_ALL = 2;
public static final int FILTER_APPS_ENABLED = 3;
- public static final int FILTER_APPS_DISABLED = 4;
- public static final int FILTER_APPS_INSTANT = 5;
+ public static final int FILTER_APPS_INSTANT = 4;
+ public static final int FILTER_APPS_DISABLED = 5;
public static final int FILTER_APPS_BLOCKED = 6;
public static final int FILTER_APPS_PERSONAL = 7;
public static final int FILTER_APPS_WORK = 8;
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index 2a5ac17..57f7345 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -18,6 +18,7 @@
import android.app.Activity;
import android.app.LoaderManager;
+import android.app.usage.StorageStatsManager;
import android.content.Context;
import android.content.Loader;
import android.graphics.drawable.Drawable;
@@ -29,6 +30,7 @@
import android.provider.SearchIndexableResource;
import android.support.annotation.VisibleForTesting;
import android.util.SparseArray;
+import android.view.View;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
@@ -44,9 +46,11 @@
import com.android.settings.deviceinfo.storage.StorageItemPreferenceController;
import com.android.settings.deviceinfo.storage.StorageSummaryDonutPreferenceController;
import com.android.settings.deviceinfo.storage.UserIconLoader;
+import com.android.settings.deviceinfo.storage.VolumeSizesLoader;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settingslib.applications.StorageStatsSource;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
import java.util.ArrayList;
@@ -58,9 +62,12 @@
private static final String TAG = "StorageDashboardFrag";
private static final int STORAGE_JOB_ID = 0;
private static final int ICON_JOB_ID = 1;
+ private static final int VOLUME_SIZE_JOB_ID = 2;
private static final int OPTIONS_MENU_MIGRATE_DATA = 100;
private VolumeInfo mVolume;
+ private PrivateStorageInfo mStorageInfo;
+ private SparseArray<StorageAsyncLoader.AppsStorageResult> mAppsResult;
private StorageSummaryDonutPreferenceController mSummaryController;
private StorageItemPreferenceController mPreferenceController;
@@ -81,29 +88,6 @@
}
initializeOptionsMenu(activity);
-
- final long sharedDataSize = mVolume.getPath().getTotalSpace();
- long totalSize = sm.getPrimaryStorageSize();
- long systemSize = totalSize - sharedDataSize;
-
- if (totalSize <= 0) {
- totalSize = sharedDataSize;
- systemSize = 0;
- }
-
- final long usedBytes = totalSize - mVolume.getPath().getFreeSpace();
- mSummaryController.updateBytes(usedBytes, totalSize);
- mPreferenceController.setVolume(mVolume);
- mPreferenceController.setUsedSize(usedBytes);
- mPreferenceController.setTotalSize(totalSize);
- for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
- PreferenceController controller = mSecondaryUsers.get(i);
- if (controller instanceof SecondaryUserController) {
- SecondaryUserController userController = (SecondaryUserController) controller;
- userController.setTotalSize(totalSize);
-
- }
- }
}
@VisibleForTesting
@@ -116,10 +100,40 @@
}
@Override
+ public void onViewCreated(View v, Bundle savedInstanceState) {
+ super.onViewCreated(v, savedInstanceState);
+ setLoading(true, false);
+ }
+
+ @Override
public void onResume() {
super.onResume();
getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
getLoaderManager().initLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks());
+ getLoaderManager().initLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks());
+ }
+
+ private void onReceivedSizes() {
+ if (mStorageInfo == null || mAppsResult == null) {
+ return;
+ }
+
+ long privateUsedBytes = mStorageInfo.totalBytes - mStorageInfo.freeBytes;
+ mSummaryController.updateBytes(privateUsedBytes, mStorageInfo.totalBytes);
+ mPreferenceController.setVolume(mVolume);
+ mPreferenceController.setUsedSize(privateUsedBytes);
+ mPreferenceController.setTotalSize(mStorageInfo.totalBytes);
+ for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
+ PreferenceController controller = mSecondaryUsers.get(i);
+ if (controller instanceof SecondaryUserController) {
+ SecondaryUserController userController = (SecondaryUserController) controller;
+ userController.setTotalSize(mStorageInfo.totalBytes);
+ }
+ }
+
+ mPreferenceController.onLoadFinished(mAppsResult.get(UserHandle.myUserId()));
+ updateSecondaryUserControllers(mSecondaryUsers, mAppsResult);
+ setLoading(false, true);
}
@Override
@@ -224,8 +238,8 @@
@Override
public void onLoadFinished(Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader,
SparseArray<StorageAsyncLoader.AppsStorageResult> data) {
- mPreferenceController.onLoadFinished(data.get(UserHandle.myUserId()));
- updateSecondaryUserControllers(mSecondaryUsers, data);
+ mAppsResult = data;
+ onReceivedSizes();
}
@Override
@@ -260,4 +274,31 @@
@Override
public void onLoaderReset(Loader<SparseArray<Drawable>> loader) {}
}
+
+ public final class VolumeSizeCallbacks
+ implements LoaderManager.LoaderCallbacks<PrivateStorageInfo> {
+ @Override
+ public Loader<PrivateStorageInfo> onCreateLoader(int id, Bundle args) {
+ Context context = getContext();
+ StorageManager sm = context.getSystemService(StorageManager.class);
+ StorageManagerVolumeProvider smvp = new StorageManagerVolumeProvider(sm);
+ final StorageStatsManager stats = context.getSystemService(StorageStatsManager.class);
+ return new VolumeSizesLoader(context, smvp, stats, mVolume);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<PrivateStorageInfo> loader) {}
+
+ @Override
+ public void onLoadFinished(
+ Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo) {
+ if (privateStorageInfo == null) {
+ getActivity().finish();
+ return;
+ }
+
+ mStorageInfo = privateStorageInfo;
+ onReceivedSizes();
+ }
+ }
}
diff --git a/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
index 890d99e..91c4a6b 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
@@ -34,6 +34,7 @@
public class StorageSummaryDonutPreferenceController extends PreferenceController {
private long mUsedBytes;
private long mTotalBytes;
+ private StorageSummaryDonutPreference mSummary;
public StorageSummaryDonutPreferenceController(Context context) {
super(context);
@@ -41,9 +42,8 @@
@Override
public void displayPreference(PreferenceScreen screen) {
- StorageSummaryDonutPreference summary = (StorageSummaryDonutPreference)
- screen.findPreference("pref_summary");
- summary.setEnabled(true);
+ mSummary = (StorageSummaryDonutPreference) screen.findPreference("pref_summary");
+ mSummary.setEnabled(true);
}
@Override
@@ -61,6 +61,13 @@
summary.setEnabled(true);
}
+ /** Invalidates the data on the view and re-renders. */
+ public void invalidateData() {
+ if (mSummary != null) {
+ updateState(mSummary);
+ }
+ }
+
@Override
public boolean isAvailable() {
return true;
@@ -79,6 +86,7 @@
public void updateBytes(long used, long total) {
mUsedBytes = used;
mTotalBytes = total;
+ invalidateData();
}
/**
diff --git a/src/com/android/settings/deviceinfo/storage/VolumeSizesLoader.java b/src/com/android/settings/deviceinfo/storage/VolumeSizesLoader.java
new file mode 100644
index 0000000..720f151
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/storage/VolumeSizesLoader.java
@@ -0,0 +1,68 @@
+/*
+ * 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.deviceinfo.storage;
+
+import android.app.usage.StorageStatsManager;
+import android.content.Context;
+import android.os.storage.VolumeInfo;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.settings.utils.AsyncLoader;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+import java.io.IOException;
+
+public class VolumeSizesLoader extends AsyncLoader<PrivateStorageInfo> {
+ private StorageVolumeProvider mVolumeProvider;
+ private StorageStatsManager mStats;
+ private VolumeInfo mVolume;
+
+ public VolumeSizesLoader(
+ Context context,
+ StorageVolumeProvider volumeProvider,
+ StorageStatsManager stats,
+ VolumeInfo volume) {
+ super(context);
+ mVolumeProvider = volumeProvider;
+ mStats = stats;
+ mVolume = volume;
+ }
+
+ @Override
+ protected void onDiscardResult(PrivateStorageInfo result) {}
+
+ @Override
+ public PrivateStorageInfo loadInBackground() {
+ PrivateStorageInfo volumeSizes;
+ try {
+ volumeSizes = getVolumeSize(mVolumeProvider, mStats, mVolume);
+ } catch (IOException e) {
+ return null;
+ }
+ return volumeSizes;
+ }
+
+ @VisibleForTesting
+ static PrivateStorageInfo getVolumeSize(
+ StorageVolumeProvider storageVolumeProvider, StorageStatsManager stats, VolumeInfo info)
+ throws IOException {
+ long privateTotalBytes = storageVolumeProvider.getTotalBytes(stats, info);
+ long privateFreeBytes = storageVolumeProvider.getFreeBytes(stats, info);
+ return new PrivateStorageInfo(privateFreeBytes, privateTotalBytes);
+ }
+}
diff --git a/src/com/android/settings/display/ThemePreferenceController.java b/src/com/android/settings/display/ThemePreferenceController.java
index a8d47d6..b535993 100644
--- a/src/com/android/settings/display/ThemePreferenceController.java
+++ b/src/com/android/settings/display/ThemePreferenceController.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.om.IOverlayManager;
import android.content.om.OverlayInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.RemoteException;
@@ -35,6 +36,7 @@
import libcore.util.Objects;
+import java.util.ArrayList;
import java.util.List;
public class ThemePreferenceController extends PreferenceController implements
@@ -109,12 +111,22 @@
return true;
}
+ private boolean isChangeableOverlay(String packageName) {
+ try {
+ PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
+ return pi != null && !pi.isStaticOverlay;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
private String getTheme() {
try {
List<OverlayInfo> infos = mOverlayService.getOverlayInfosForTarget("android",
UserHandle.myUserId());
for (int i = 0, size = infos.size(); i < size; i++) {
- if (infos.get(i).isEnabled()) {
+ if (infos.get(i).isEnabled() &&
+ isChangeableOverlay(infos.get(i).packageName)) {
return infos.get(i).packageName;
}
}
@@ -141,11 +153,13 @@
try {
List<OverlayInfo> infos = mOverlayService.getOverlayInfosForTarget("android",
UserHandle.myUserId());
- String[] pkgs = new String[infos.size()];
+ List<String> pkgs = new ArrayList(infos.size());
for (int i = 0, size = infos.size(); i < size; i++) {
- pkgs[i] = infos.get(i).packageName;
+ if (isChangeableOverlay(infos.get(i).packageName)) {
+ pkgs.add(infos.get(i).packageName);
+ }
}
- return pkgs;
+ return pkgs.toArray(new String[pkgs.size()]);
} catch (RemoteException e) {
}
return new String[0];
diff --git a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java
index f7cb191..49a0179 100644
--- a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java
@@ -60,6 +60,7 @@
import com.android.settings.enterprise.DevicePolicyManagerWrapper;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
import java.util.ArrayList;
@@ -155,7 +156,8 @@
@Override
public boolean isAvailable() {
- return mAppEntry != null;
+ // TODO(b/37313605): Re-enable once this controller supports instant apps
+ return mAppEntry != null && !AppUtils.isInstant(mAppEntry.info);
}
@Override
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 0000000..ef36e5f
--- /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 =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<licenses>\n" +
+ "<file-name contentId=\"0\">/file0</file-name>\n" +
+ "<file-name contentId=\"0\">/file1</file-name>\n" +
+ "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n" +
+ "</licenses>";
+
+ private static final String INVAILD_XML_STRING =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<licenses2>\n" +
+ "<file-name contentId=\"0\">/file0</file-name>\n" +
+ "<file-name contentId=\"0\">/file1</file-name>\n" +
+ "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n" +
+ "</licenses2>";
+
+ private static final String EXPECTED_HTML_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>\n" +
+ "<li><a href=\"#id0\">/file0</a></li>\n" +
+ "<li><a href=\"#id0\">/file1</a></li>\n" +
+ "</ul>\n" +
+ "</div><!-- table of contents -->\n" +
+ "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n" +
+ "<tr id=\"id0\"><td class=\"same-license\">\n" +
+ "<div class=\"label\">Notices for file(s):</div>\n" +
+ "<div class=\"file-list\">\n" +
+ "/file0 <br/>\n" +
+ "/file1 <br/>\n" +
+ "</div><!-- file-list -->\n" +
+ "<pre class=\"license-text\">\n" +
+ "license content #0\n" +
+ "</pre><!-- license-text -->\n" +
+ "</td></tr><!-- same-license -->\n" +
+ "</table></body></html>\n";
+
+ @Test
+ public void testParseValidXmlStream() throws XmlPullParserException, IOException {
+ Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+ 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<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+ LicenseHtmlGeneratorFromXml.parse(
+ new InputStreamReader(new ByteArrayInputStream(INVAILD_XML_STRING.getBytes())),
+ fileNameToContentIdMap, contentIdToFileContentMap);
+ }
+
+ @Test
+ public void testGenerateHtml() {
+ Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+ 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 0000000..96e88c2
--- /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<File> 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<File> 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<File> 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<File> 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<File> 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 0000000..3e28a2a
--- /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<SettingsLicenseActivity> 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);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/VolumeSizesLoaderTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/VolumeSizesLoaderTest.java
new file mode 100644
index 0000000..3f9ccaf
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/VolumeSizesLoaderTest.java
@@ -0,0 +1,30 @@
+package com.android.settings.deviceinfo.storage;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.storage.VolumeInfo;
+
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class VolumeSizesLoaderTest {
+ @Test
+ public void getVolumeSize_getsValidSizes() throws Exception {
+ VolumeInfo info = mock(VolumeInfo.class);
+ StorageVolumeProvider storageVolumeProvider = mock(StorageVolumeProvider.class);
+ when(storageVolumeProvider.getTotalBytes(any(), any())).thenReturn(10000L);
+ when(storageVolumeProvider.getFreeBytes(any(), any())).thenReturn(1000L);
+
+ PrivateStorageInfo storageInfo =
+ VolumeSizesLoader.getVolumeSize(storageVolumeProvider, null, info);
+
+ assertThat(storageInfo.freeBytes).isEqualTo(1000L);
+ assertThat(storageInfo.totalBytes).isEqualTo(10000L);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java
index 85b4f81..a341d83 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java
@@ -19,8 +19,8 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -48,7 +48,9 @@
import com.android.settings.core.lifecycle.Lifecycle;
import com.android.settings.enterprise.DevicePolicyManagerWrapper;
import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +63,7 @@
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -189,6 +192,32 @@
}
@Test
+ public void testIsAvailable_nonInstantApp() throws Exception {
+ mController.mAppEntry = mAppEntry;
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ new InstantAppDataProvider() {
+ @Override
+ public boolean isInstantApp(ApplicationInfo info) {
+ return false;
+ }
+ });
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void testIsAvailable_instantApp() throws Exception {
+ mController.mAppEntry = mAppEntry;
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ new InstantAppDataProvider() {
+ @Override
+ public boolean isInstantApp(ApplicationInfo info) {
+ return true;
+ }
+ });
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
public void testUpdateUninstallButton_isDeviceAdminApp_setButtonDisable() {
doReturn(true).when(mController).handleDisableable(any());
mAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
diff --git a/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java b/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java
index 3137d59..69c8c54 100644
--- a/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java
@@ -16,11 +16,10 @@
package com.android.settings.display;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -29,6 +28,7 @@
import android.content.ContextWrapper;
import android.content.om.OverlayInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -58,7 +58,7 @@
public void setup() {
mMockOverlayManager = mock(OverlayManager.class);
mMockPackageManager = mock(PackageManager.class);
- mContext = new ContextWrapper(InstrumentationRegistry.getContext()) {
+ mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()) {
@Override
public PackageManager getPackageManager() {
return mMockPackageManager;
@@ -82,6 +82,8 @@
}
return info;
});
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(
+ new PackageInfo());
when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt())).thenReturn(
list(info1, info2));
ListPreference pref = mock(ListPreference.class);
@@ -91,31 +93,71 @@
CharSequence[] entries = arg.getValue();
- assertEquals(2, entries.length);
- assertEquals("Theme1", entries[0]);
- assertEquals("Theme2", entries[1]);
+ assertThat(entries).asList().containsExactly("Theme1", "Theme2");
verify(pref).setEntryValues(arg.capture());
CharSequence[] entryValues = arg.getValue();
- assertEquals("com.android.Theme1", entryValues[0]);
- assertEquals("com.android.Theme2", entryValues[1]);
+ assertThat(entryValues).asList().containsExactly(
+ "com.android.Theme1", "com.android.Theme2");
verify(pref).setValue(eq("com.android.Theme1"));
}
@Test
+ public void testUpdateState_withStaticOverlay() throws Exception {
+ OverlayInfo info1 = new OverlayInfo("com.android.Theme1", "android",
+ "", OverlayInfo.STATE_ENABLED, 0);
+ OverlayInfo info2 = new OverlayInfo("com.android.Theme2", "android",
+ "", OverlayInfo.STATE_ENABLED, 0);
+ when(mMockPackageManager.getApplicationInfo(any(), anyInt())).thenAnswer(inv -> {
+ ApplicationInfo info = mock(ApplicationInfo.class);
+ if ("com.android.Theme1".equals(inv.getArguments()[0])) {
+ when(info.loadLabel(any())).thenReturn("Theme1");
+ } else {
+ when(info.loadLabel(any())).thenReturn("Theme2");
+ }
+ return info;
+ });
+ PackageInfo pi = new PackageInfo();
+ pi.isStaticOverlay = true;
+ when(mMockPackageManager.getPackageInfo(eq("com.android.Theme1"), anyInt())).thenReturn(pi);
+ when(mMockPackageManager.getPackageInfo(eq("com.android.Theme2"), anyInt())).thenReturn(
+ new PackageInfo());
+ when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt())).thenReturn(
+ list(info1, info2));
+ ListPreference pref = mock(ListPreference.class);
+ mPreferenceController.updateState(pref);
+ ArgumentCaptor<String[]> arg = ArgumentCaptor.forClass(String[].class);
+ verify(pref).setEntries(arg.capture());
+
+
+ CharSequence[] entries = arg.getValue();
+ assertThat(entries).asList().containsExactly("Theme2");
+
+ verify(pref).setEntryValues(arg.capture());
+ CharSequence[] entryValues = arg.getValue();
+ assertThat(entryValues).asList().containsExactly("com.android.Theme2");
+
+ verify(pref).setValue(eq("com.android.Theme2"));
+ }
+
+ @Test
public void testAvailable_false() throws Exception {
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(
+ new PackageInfo());
when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt()))
.thenReturn(list(new OverlayInfo("", "", "", 0, 0)));
- assertFalse(mPreferenceController.isAvailable());
+ assertThat(mPreferenceController.isAvailable()).isFalse();
}
@Test
public void testAvailable_true() throws Exception {
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(
+ new PackageInfo());
when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt()))
.thenReturn(list(new OverlayInfo("", "", "", 0, 0),
new OverlayInfo("", "", "", 0, 0)));
- assertTrue(mPreferenceController.isAvailable());
+ assertThat(mPreferenceController.isAvailable()).isTrue();
}
private ArrayList<OverlayInfo> list(OverlayInfo... infos) {