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) {