Merge "Import translations. DO NOT MERGE"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2343ac2..7abdf27 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2952,6 +2952,11 @@
                        android:value="com.android.settings.deletionhelper.AutomaticStorageManagerSettings" />
         </activity>
 
+        <activity android:name="Settings$LegacySupportActivity">
+            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+            android:value="com.android.settings.dashboard.SupportFragment"/>
+        </activity>
+
         <!-- Information architecture host activities -->
 
         <!-- Alias for battery settings in new IA. Remove and merge metadata into TargetActivity -->
@@ -3001,17 +3006,16 @@
                        android:resource="@string/system_dashboard_summary"/>
         </activity>
 
-        <activity android:name=".Settings$SupportDashboardActivity"
+        <activity android:name=".dashboard.SupportDashboardActivity"
                   android:label="@string/page_tab_title_support"
                   android:icon="@drawable/ic_help"
+                  android:theme="@android:style/Theme.NoDisplay"
                   android:enabled="@bool/config_support_enabled">
             <intent-filter android:priority="-2">
                 <action android:name="com.android.settings.action.SETTINGS"/>
             </intent-filter>
             <meta-data android:name="com.android.settings.category"
                        android:value="com.android.settings.category.ia.homepage"/>
-            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
-                       android:value="com.android.settings.dashboard.SupportFragment"/>
             <meta-data android:name="com.android.settings.summary"
                        android:resource="@string/support_summary"/>
         </activity>
diff --git a/res/layout/dream_info_row.xml b/res/layout/dream_info_row.xml
deleted file mode 100644
index fd70ad5..0000000
--- a/res/layout/dream_info_row.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<!--
-     Copyright (C) 2012 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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:clickable="true"
-    android:focusable="true"
-    android:orientation="horizontal"
-    android:background="?android:attr/selectableItemBackground" >
-
-    <!-- Dream icon -->
-
-    <ImageView
-        android:id="@android:id/icon"
-        android:layout_width="@android:dimen/app_icon_size"
-        android:layout_height="@android:dimen/app_icon_size"
-        android:layout_centerVertical="true"
-        android:layout_marginBottom="10dp"
-        android:layout_marginStart="0dp"
-        android:layout_marginEnd="6dp"
-        android:layout_marginTop="10dp"
-        android:contentDescription="@null"
-        android:maxHeight="@android:dimen/app_icon_size"
-        android:maxWidth="@android:dimen/app_icon_size"
-        android:scaleType="fitCenter" />
-
-    <!-- Dream caption -->
-
-    <TextView
-        android:id="@android:id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:layout_centerVertical="true"
-        android:ellipsize="end"
-        android:singleLine="true"
-        android:gravity="center_vertical"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textAlignment="viewStart"
-        android:labelFor="@android:id/button2" />
-
-    <!-- Dream radio button -->
-
-    <RadioButton
-        android:id="@android:id/button1"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_alignParentEnd="true"
-        android:layout_centerVertical="true"
-        android:duplicateParentState="true"
-        android:clickable="false"
-        android:focusable="false" />
-
-    <!-- Divider -->
-
-    <ImageView
-        android:id="@+id/divider"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_centerVertical="true"
-        android:contentDescription="@null"
-        android:src="@drawable/nav_divider" />
-
-    <!-- Settings icon -->
-
-    <ImageView
-        android:id="@android:id/button2"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_centerVertical="true"
-        android:layout_margin="0dip"
-        android:background="?android:attr/selectableItemBackground"
-        android:contentDescription="@string/screensaver_settings_button"
-        android:padding="8dip"
-        android:clickable="true"
-        android:focusable="true"
-        android:src="@drawable/ic_settings" />
-
-</LinearLayout>
diff --git a/res/layout/dream_start_button.xml b/res/layout/dream_start_button.xml
new file mode 100644
index 0000000..7d5e0c9
--- /dev/null
+++ b/res/layout/dream_start_button.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:gravity="bottom"
+    android:paddingTop="4dp"
+    android:paddingStart="72dp"
+    android:paddingEnd="72dp"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <Button
+        android:id="@+id/dream_start_now_button"
+        style="@style/DreamStartButton"
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="@string/screensaver_settings_dream_start"
+        android:paddingEnd="8dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/preference_dropdown_material_settings.xml b/res/layout/preference_dropdown_material_settings.xml
new file mode 100644
index 0000000..22f98b6
--- /dev/null
+++ b/res/layout/preference_dropdown_material_settings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2016 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.
+  -->
+
+
+<!-- Based off frameworks/base/core/res/res/layout/preference_dropdown_material.xml
+     except that icon space in this layout is always reserved -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <Spinner
+        android:id="@+id/spinner"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/preference_no_icon_padding_start"
+        android:visibility="invisible" />
+
+    <include layout="@layout/preference_material_settings"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 873576c..5e7442b 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1056,4 +1056,18 @@
         <item>90</item>
     </string-array>
 
+    <string-array name="when_to_start_screensaver_entries" translatable="false">
+        <item>@string/screensaver_settings_summary_sleep</item>
+        <item>@string/screensaver_settings_summary_dock</item>
+        <item>@string/screensaver_settings_summary_either_long</item>
+        <item>@string/screensaver_settings_summary_never</item>
+    </string-array>
+
+    <string-array name="when_to_start_screensaver_values" translatable="false">
+        <item>while_charging_only</item>
+        <item>while_docked_only</item>
+        <item>either_charging_or_docked</item>
+        <item>never</item>
+    </string-array>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c92d0f0..8be8e42 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8716,5 +8716,6 @@
     <!-- Trigger Carrier Provisioning [CHAR LIMIT=NONE] -->
     <string name="trigger_carrier_provisioning">Trigger Carrier Provisioning</string>
 
-
+    <!-- Help URI, USB Audio [DO NOT TRANSLATE] -->
+    <string name="help_url_audio_accessory_not_supported" translatable="false"></string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index bae36b3..0780e82 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -421,6 +421,8 @@
 
     <style name="AppActionPrimaryButton" parent="android:Widget.Material.Button.Colored"/>
 
+    <style name="DreamStartButton" parent="android:Widget.Material.Button" />
+
     <style name="LockPatternStyle">
         <item name="*android:regularColor">@color/lock_pattern_view_regular_color</item>
         <item name="*android:successColor">@color/lock_pattern_view_success_color</item>
diff --git a/res/values/styles_preference.xml b/res/values/styles_preference.xml
index a5e8b60..9c7fca9 100644
--- a/res/values/styles_preference.xml
+++ b/res/values/styles_preference.xml
@@ -52,7 +52,9 @@
         <item name="android:dialogLayout">@layout/preference_dialog_edittext</item>
     </style>
 
-    <style name="SettingsDropdownPreference" parent="SettingsPreference" />
+    <style name="SettingsDropdownPreference" parent="SettingsPreference">
+        <item name="android:layout">@layout/preference_dropdown_material_settings</item>
+    </style>
 
     <style name="SettingsDialogPreference" parent="SettingsPreference" />
 
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index 9d4acb4..8815665 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -76,7 +76,7 @@
     <Preference
         android:key="screensaver"
         android:title="@string/screensaver_settings_title"
-        android:fragment="com.android.settings.DreamSettings" />
+        android:fragment="com.android.settings.dream.DreamSettings" />
 
     <!-- Hide night mode for now
     <ListPreference
diff --git a/res/xml/dream_fragment_overview.xml b/res/xml/dream_fragment_overview.xml
new file mode 100644
index 0000000..d27a6e3
--- /dev/null
+++ b/res/xml/dream_fragment_overview.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+    android:title="@string/screensaver_settings_title" >
+
+    <com.android.settings.widget.GearPreference
+        android:key="current_screensaver"
+        android:title="@string/screensaver_settings_current"
+        android:fragment="com.android.settings.dream.CurrentDreamPicker" />
+
+    <Preference
+        android:key="when_to_start"
+        android:title="@string/screensaver_settings_when_to_dream"
+        android:fragment="com.android.settings.dream.WhenToDreamPicker" />
+
+
+    <!-- Layout preference doesn't obey allowDividerAbove, so put it in a PreferenceCategory -->
+    <PreferenceCategory>
+        <com.android.settings.applications.LayoutPreference
+            android:key="dream_start_now_button_container"
+            android:selectable="false"
+            android:layout="@layout/dream_start_button" />
+    </PreferenceCategory>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml
index abd659e..3c685c0 100644
--- a/res/xml/power_usage_summary.xml
+++ b/res/xml/power_usage_summary.xml
@@ -25,6 +25,9 @@
         android:selectable="true"
         android:layout="@layout/battery_header"/>
 
+    <Preference
+        android:key="high_usage"/>
+
     <PreferenceCategory
         android:key="device_usage_list">
 
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/DreamSettings.java b/src/com/android/settings/DreamSettings.java
deleted file mode 100644
index bb9e978..0000000
--- a/src/com/android/settings/DreamSettings.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Copyright (C) 2012 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.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceViewHolder;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnTouchListener;
-import android.widget.ImageView;
-import android.widget.RadioButton;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.widget.SwitchBar;
-import com.android.settingslib.dream.DreamBackend;
-import com.android.settingslib.dream.DreamBackend.DreamInfo;
-
-import java.util.List;
-
-public class DreamSettings extends SettingsPreferenceFragment implements
-        SwitchBar.OnSwitchChangeListener {
-    private static final String TAG = DreamSettings.class.getSimpleName();
-    static final boolean DEBUG = false;
-    private static final int DIALOG_WHEN_TO_DREAM = 1;
-    private static final String PACKAGE_SCHEME = "package";
-
-    private final PackageReceiver mPackageReceiver = new PackageReceiver();
-
-    private Context mContext;
-    private DreamBackend mBackend;
-    private SwitchBar mSwitchBar;
-    private MenuItem[] mMenuItemsWhenEnabled;
-    private boolean mRefreshing;
-
-    @Override
-    public int getHelpResource() {
-        return R.string.help_url_dreams;
-    }
-
-    @Override
-    public void onAttach(Activity activity) {
-        logd("onAttach(%s)", activity.getClass().getSimpleName());
-        super.onAttach(activity);
-        mContext = activity;
-    }
-
-    @Override
-    public int getMetricsCategory() {
-        return MetricsEvent.DREAM;
-    }
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        logd("onCreate(%s)", icicle);
-        super.onCreate(icicle);
-
-        mBackend = new DreamBackend(getActivity());
-
-        setHasOptionsMenu(true);
-    }
-
-    @Override
-    public void onSwitchChanged(Switch switchView, boolean isChecked) {
-        if (!mRefreshing) {
-            mBackend.setEnabled(isChecked);
-            refreshFromBackend();
-        }
-    }
-
-    @Override
-    public void onStart() {
-        logd("onStart()");
-        super.onStart();
-    }
-
-    @Override
-    public void onDestroyView() {
-        logd("onDestroyView()");
-        super.onDestroyView();
-
-        mSwitchBar.removeOnSwitchChangeListener(this);
-        mSwitchBar.hide();
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        logd("onActivityCreated(%s)", savedInstanceState);
-        super.onActivityCreated(savedInstanceState);
-
-        TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
-        emptyView.setText(R.string.screensaver_settings_disabled_prompt);
-        setEmptyView(emptyView);
-
-        final SettingsActivity sa = (SettingsActivity) getActivity();
-        mSwitchBar = sa.getSwitchBar();
-        mSwitchBar.addOnSwitchChangeListener(this);
-        mSwitchBar.show();
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        logd("onCreateOptionsMenu()");
-
-        boolean isEnabled = mBackend.isEnabled();
-
-        // create "start" action
-        MenuItem start = createMenuItem(menu, R.string.screensaver_settings_dream_start,
-                MenuItem.SHOW_AS_ACTION_NEVER,
-                isEnabled, new Runnable(){
-                    @Override
-                    public void run() {
-                        mBackend.startDreaming();
-                    }});
-
-        // create "when to dream" overflow menu item
-        MenuItem whenToDream = createMenuItem(menu,
-                R.string.screensaver_settings_when_to_dream,
-                MenuItem.SHOW_AS_ACTION_NEVER,
-                isEnabled,
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        showDialog(DIALOG_WHEN_TO_DREAM);
-                    }});
-
-        // create "help" overflow menu item (make sure it appears last)
-        super.onCreateOptionsMenu(menu, inflater);
-
-        mMenuItemsWhenEnabled = new MenuItem[] { start, whenToDream };
-    }
-
-    private MenuItem createMenuItem(Menu menu,
-            int titleRes, int actionEnum, boolean isEnabled, final Runnable onClick) {
-        MenuItem item = menu.add(titleRes);
-        item.setShowAsAction(actionEnum);
-        item.setEnabled(isEnabled);
-        item.setOnMenuItemClickListener(new OnMenuItemClickListener() {
-            @Override
-            public boolean onMenuItemClick(MenuItem item) {
-                onClick.run();
-                return true;
-            }
-        });
-        return item;
-    }
-
-    @Override
-    public Dialog onCreateDialog(int dialogId) {
-        logd("onCreateDialog(%s)", dialogId);
-        if (dialogId == DIALOG_WHEN_TO_DREAM)
-            return createWhenToDreamDialog();
-        return super.onCreateDialog(dialogId);
-    }
-
-    @Override
-    public int getDialogMetricsCategory(int dialogId) {
-        if (dialogId == DIALOG_WHEN_TO_DREAM) {
-            return MetricsEvent.DIALOG_DREAM_START_DELAY;
-        }
-        return 0;
-    }
-
-    private Dialog createWhenToDreamDialog() {
-        final CharSequence[] items = {
-                mContext.getString(R.string.screensaver_settings_summary_dock),
-                mContext.getString(R.string.screensaver_settings_summary_sleep),
-                mContext.getString(R.string.screensaver_settings_summary_either_short)
-        };
-
-        int initialSelection = mBackend.isActivatedOnDock() && mBackend.isActivatedOnSleep() ? 2
-                : mBackend.isActivatedOnDock() ? 0
-                : mBackend.isActivatedOnSleep() ? 1
-                : -1;
-
-        return new AlertDialog.Builder(mContext)
-                .setTitle(R.string.screensaver_settings_when_to_dream)
-                .setSingleChoiceItems(items, initialSelection, new DialogInterface.OnClickListener() {
-                    public void onClick(DialogInterface dialog, int item) {
-                        mBackend.setActivatedOnDock(item == 0 || item == 2);
-                        mBackend.setActivatedOnSleep(item == 1 || item == 2);
-                        dialog.dismiss();
-                    }
-                })
-                .create();
-    }
-
-    @Override
-    public void onPause() {
-        logd("onPause()");
-        super.onPause();
-
-        mContext.unregisterReceiver(mPackageReceiver);
-    }
-
-    @Override
-    public void onResume() {
-        logd("onResume()");
-        super.onResume();
-        refreshFromBackend();
-
-        // listen for package changes
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        filter.addDataScheme(PACKAGE_SCHEME);
-        mContext.registerReceiver(mPackageReceiver , filter);
-    }
-
-    public static int getSummaryResource(Context context) {
-        DreamBackend backend = new DreamBackend(context);
-        boolean isEnabled = backend.isEnabled();
-        boolean activatedOnSleep = backend.isActivatedOnSleep();
-        boolean activatedOnDock = backend.isActivatedOnDock();
-        boolean activatedOnEither = activatedOnSleep && activatedOnDock;
-        return !isEnabled ? R.string.screensaver_settings_summary_off
-                : activatedOnEither ? R.string.screensaver_settings_summary_either_long
-                : activatedOnSleep ? R.string.screensaver_settings_summary_sleep
-                : activatedOnDock ? R.string.screensaver_settings_summary_dock
-                : 0;
-    }
-
-    public static CharSequence getSummaryTextWithDreamName(Context context) {
-        DreamBackend backend = new DreamBackend(context);
-        boolean isEnabled = backend.isEnabled();
-        if (!isEnabled) {
-            return context.getString(R.string.screensaver_settings_summary_off);
-        } else {
-            return backend.getActiveDreamName();
-        }
-    }
-
-    private void refreshFromBackend() {
-        logd("refreshFromBackend()");
-        mRefreshing = true;
-        boolean dreamsEnabled = mBackend.isEnabled();
-        if (mSwitchBar.isChecked() != dreamsEnabled) {
-            mSwitchBar.setChecked(dreamsEnabled);
-        }
-
-        if (getPreferenceScreen() == null) {
-            setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
-        }
-        getPreferenceScreen().removeAll();
-        if (dreamsEnabled) {
-            List<DreamBackend.DreamInfo> dreamInfos = mBackend.getDreamInfos();
-            final int N = dreamInfos.size();
-            for (int i = 0; i < N; i++) {
-                getPreferenceScreen().addPreference(
-                        new DreamInfoPreference(getPrefContext(), dreamInfos.get(i)));
-            }
-        }
-        if (mMenuItemsWhenEnabled != null) {
-            for (MenuItem menuItem : mMenuItemsWhenEnabled) {
-                menuItem.setEnabled(dreamsEnabled);
-            }
-        }
-        mRefreshing = false;
-    }
-
-    private static void logd(String msg, Object... args) {
-        if (DEBUG) Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
-    }
-
-    private class DreamInfoPreference extends Preference {
-
-        private final DreamInfo mInfo;
-
-        public DreamInfoPreference(Context context, DreamInfo info) {
-            super(context);
-            mInfo = info;
-            setLayoutResource(R.layout.dream_info_row);
-            setTitle(mInfo.caption);
-            setIcon(mInfo.icon);
-        }
-
-        public void onBindViewHolder(final PreferenceViewHolder holder) {
-            super.onBindViewHolder(holder);
-
-            // bind radio button
-            RadioButton radioButton = (RadioButton) holder.findViewById(android.R.id.button1);
-            radioButton.setChecked(mInfo.isActive);
-            radioButton.setOnTouchListener(new OnTouchListener() {
-                @Override
-                public boolean onTouch(View v, MotionEvent event) {
-                    holder.itemView.onTouchEvent(event);
-                    return false;
-                }
-            });
-
-            // bind settings button + divider
-            boolean showSettings = mInfo.settingsComponentName != null;
-            View settingsDivider = holder.findViewById(R.id.divider);
-            settingsDivider.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE);
-
-            ImageView settingsButton = (ImageView) holder.findViewById(android.R.id.button2);
-            settingsButton.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE);
-            settingsButton.setAlpha(mInfo.isActive ? 1f : Utils.DISABLED_ALPHA);
-            settingsButton.setEnabled(mInfo.isActive);
-            settingsButton.setFocusable(mInfo.isActive);
-            settingsButton.setOnClickListener(new OnClickListener(){
-                @Override
-                public void onClick(View v) {
-                    mBackend.launchSettings(mInfo);
-                }
-            });
-        }
-
-        @Override
-        public void performClick() {
-            if (mInfo.isActive)
-                return;
-            for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
-                DreamInfoPreference preference =
-                        (DreamInfoPreference) getPreferenceScreen().getPreference(i);
-                preference.mInfo.isActive = false;
-                preference.notifyChanged();
-            }
-            mInfo.isActive = true;
-            mBackend.setActiveDream(mInfo.componentName);
-            notifyChanged();
-        }
-    }
-
-    private class PackageReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            logd("PackageReceiver.onReceive");
-            refreshFromBackend();
-        }
-    }
-}
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/RadioInfo.java b/src/com/android/settings/RadioInfo.java
index 880f3fe..406d293 100644
--- a/src/com/android/settings/RadioInfo.java
+++ b/src/com/android/settings/RadioInfo.java
@@ -412,7 +412,7 @@
         imsVolteProvisionedSwitch = (Switch) findViewById(R.id.volte_provisioned_switch);
         imsVtProvisionedSwitch = (Switch) findViewById(R.id.vt_provisioned_switch);
         imsWfcProvisionedSwitch = (Switch) findViewById(R.id.wfc_provisioned_switch);
-        eabProvisionedSwitch = (Switch) findViewById(R.id.wfc_provisioned_switch);
+        eabProvisionedSwitch = (Switch) findViewById(R.id.eab_provisioned_switch);
 
         radioPowerOnSwitch = (Switch) findViewById(R.id.radio_power);
 
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index e2404ec..2e6aed0 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -169,6 +169,7 @@
         }
     }
     public static class WebViewAppPickerActivity extends SettingsActivity { /* empty */ }
+    public static class LegacySupportActivity extends SettingsActivity{ /* empty */ }
 
     // Top level categories for new IA
     public static class NetworkDashboardActivity extends SettingsActivity {}
@@ -177,6 +178,5 @@
     public static class StorageDashboardActivity extends SettingsActivity {}
     public static class UserAndAccountDashboardActivity extends SettingsActivity {}
     public static class SystemDashboardActivity extends SettingsActivity {}
-    public static class SupportDashboardActivity extends SettingsActivity {}
 
 }
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/AppAndNotificationDashboardFragment.java b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
index 51225aa..2ea61bc 100644
--- a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
+++ b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
@@ -62,6 +62,7 @@
     private static List<PreferenceController> buildPreferenceControllers(Context context) {
         final List<PreferenceController> controllers = new ArrayList<>();
         controllers.add(new SpecialAppAccessPreferenceController(context));
+        controllers.add(new AppPermissionsPreferenceController(context));
         return controllers;
     }
 
diff --git a/src/com/android/settings/applications/AppPermissionsPreferenceController.java b/src/com/android/settings/applications/AppPermissionsPreferenceController.java
new file mode 100644
index 0000000..57ec6d8
--- /dev/null
+++ b/src/com/android/settings/applications/AppPermissionsPreferenceController.java
@@ -0,0 +1,143 @@
+/*
+ * 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.applications;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import com.android.settings.R;
+import com.android.settings.core.PreferenceController;
+import java.util.List;
+import java.util.Set;
+
+public class AppPermissionsPreferenceController extends PreferenceController {
+
+    private static final String TAG = "AppPermissionPrefCtrl";
+    private static final String KEY_APP_PERMISSION_GROUPS = "manage_perms";
+    private static final String[] PERMISSION_GROUPS = new String[] {
+        "android.permission-group.LOCATION",
+        "android.permission-group.MICROPHONE",
+        "android.permission-group.CAMERA",
+        "android.permission-group.SMS",
+        "android.permission-group.CONTACTS",
+        "android.permission-group.PHONE"};
+
+    private static final int NUM_PERMISSION_TO_USE = 3;
+
+    private final PackageManager mPackageManager;
+
+    public AppPermissionsPreferenceController(Context context) {
+        super(context);
+        mPackageManager = context.getPackageManager();
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_APP_PERMISSION_GROUPS;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        final String summary = getSummary();
+        if (summary != null) {
+            preference.setSummary(summary);
+        }
+    }
+
+    /*
+       Summary text looks like: Apps using Permission1, Permission2, Permission3
+       The 3 permissions are the first three from the list which any app has granted:
+       Location, Microphone, Camera, Sms, Contacts, and Phone
+     */
+    private String getSummary() {
+        final Set<String> permissions = getAllPermissionsInGroups();
+        Set<String> grantedPermissionGroups = getGrantedPermissionGroups(permissions);
+        CharSequence summary = null;
+        int count = 0;
+        for (String group : PERMISSION_GROUPS) {
+            if (!grantedPermissionGroups.contains(group)) {
+                continue;
+            }
+            summary = concatSummaryText(summary, group);
+            if (++count >= NUM_PERMISSION_TO_USE) {
+                break;
+            }
+        }
+        return count > 0 ? mContext.getString(R.string.app_permissions_summary, summary) : null;
+    }
+
+    private Set<String> getGrantedPermissionGroups(Set<String> permissions) {
+        ArraySet<String> grantedPermissionGroups = new ArraySet<>();
+        List<PackageInfo> installedPackages =
+            mPackageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS);
+        for (PackageInfo installedPackage : installedPackages) {
+            if (installedPackage.permissions == null) {
+                continue;
+            }
+            for (PermissionInfo permissionInfo : installedPackage.permissions) {
+                if (permissions.contains(permissionInfo.name)
+                        && !grantedPermissionGroups.contains(permissionInfo.group)) {
+                    grantedPermissionGroups.add(permissionInfo.group);
+                }
+            }
+        }
+        return grantedPermissionGroups;
+    }
+
+    private CharSequence concatSummaryText(CharSequence currentSummary, String permission) {
+        final CharSequence label = getPermissionGroupLabel(permission);
+        if (TextUtils.isEmpty(currentSummary)) {
+            return label;
+        }
+        return mContext.getString(R.string.join_many_items_middle, currentSummary, label);
+    }
+
+    private CharSequence getPermissionGroupLabel(String group) {
+        try {
+            final PermissionGroupInfo groupInfo = mPackageManager.getPermissionGroupInfo(group, 0);
+            return groupInfo.loadLabel(mPackageManager);
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Error getting permissions label.", e);
+        }
+        return group;
+    }
+
+    private Set<String> getAllPermissionsInGroups() {
+        ArraySet<String> result = new ArraySet<>();
+        for (String group : PERMISSION_GROUPS) {
+            try {
+                final List<PermissionInfo> permissions =
+                    mPackageManager.queryPermissionsByGroup(group, 0);
+                for (PermissionInfo permissionInfo : permissions) {
+                    result.add(permissionInfo.name);
+                }
+            } catch (NameNotFoundException e) {
+                Log.e(TAG, "Error getting permissions in group "+group, e);
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 5d61fe5..965501f 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/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index caac95f..b8cf856 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -25,7 +25,7 @@
 import com.android.settings.DeviceAdminSettings;
 import com.android.settings.DeviceInfoSettings;
 import com.android.settings.DisplaySettings;
-import com.android.settings.DreamSettings;
+import com.android.settings.dream.DreamSettings;
 import com.android.settings.IccLockSettings;
 import com.android.settings.MasterClear;
 import com.android.settings.PrivacySettings;
@@ -80,6 +80,7 @@
 import com.android.settings.fuelgauge.BatterySaverSettings;
 import com.android.settings.fuelgauge.PowerUsageSummary;
 import com.android.settings.gestures.AssistGestureSettings;
+import com.android.settings.gestures.CameraLiftTriggerSettings;
 import com.android.settings.gestures.DoubleTapPowerSettings;
 import com.android.settings.gestures.DoubleTapScreenSettings;
 import com.android.settings.gestures.DoubleTwistGestureSettings;
@@ -177,6 +178,7 @@
             AccountSyncSettings.class.getName(),
             AssistGestureSettings.class.getName(),
             SwipeToNotificationSettings.class.getName(),
+            CameraLiftTriggerSettings.class.getName(),
             DoubleTapPowerSettings.class.getName(),
             DoubleTapScreenSettings.class.getName(),
             PickupGestureSettings.class.getName(),
@@ -260,7 +262,6 @@
             Settings.SecuritySettingsActivity.class.getName(),
             Settings.AccessibilitySettingsActivity.class.getName(),
             Settings.SystemDashboardActivity.class.getName(),
-            Settings.SupportDashboardActivity.class.getName(),
             // Home page > Network & Internet
             Settings.WifiSettingsActivity.class.getName(),
             Settings.DataUsageSummaryActivity.class.getName(),
diff --git a/src/com/android/settings/dashboard/SupportDashboardActivity.java b/src/com/android/settings/dashboard/SupportDashboardActivity.java
new file mode 100644
index 0000000..6cd6612
--- /dev/null
+++ b/src/com/android/settings/dashboard/SupportDashboardActivity.java
@@ -0,0 +1,46 @@
+/*
+ * 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.dashboard;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import com.android.settings.Settings.LegacySupportActivity;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.overlay.SupportFeatureProvider;
+
+/**
+ * Trampoline activity that decides which version of support should be shown to the user.
+ */
+public class SupportDashboardActivity extends Activity {
+
+    public SupportDashboardActivity() {}
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        SupportFeatureProvider supportFeatureProvider = FeatureFactory.getFactory(this)
+                .getSupportFeatureProvider(this);
+
+        // try to launch support v2 if we have the feature provider
+        if (supportFeatureProvider != null && supportFeatureProvider.isSupportV2Enabled()) {
+            supportFeatureProvider.startSupportV2(this);
+        } else {
+            startActivity(new Intent(this, LegacySupportActivity.class));
+        }
+        finish();
+    }
+}
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/ScreenSaverPreferenceController.java b/src/com/android/settings/display/ScreenSaverPreferenceController.java
index fab9062..7a10802 100644
--- a/src/com/android/settings/display/ScreenSaverPreferenceController.java
+++ b/src/com/android/settings/display/ScreenSaverPreferenceController.java
@@ -16,8 +16,8 @@
 import android.content.Context;
 import android.support.v7.preference.Preference;
 
-import com.android.settings.DreamSettings;
 import com.android.settings.core.PreferenceController;
+import com.android.settings.dream.DreamSettings;
 
 public class ScreenSaverPreferenceController extends PreferenceController {
 
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/dream/CurrentDreamPicker.java b/src/com/android/settings/dream/CurrentDreamPicker.java
new file mode 100644
index 0000000..da9bf9e
--- /dev/null
+++ b/src/com/android/settings/dream/CurrentDreamPicker.java
@@ -0,0 +1,117 @@
+/*
+ * 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.dream;
+
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.widget.RadioButtonPickerFragment;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.DreamInfo;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public final class CurrentDreamPicker extends RadioButtonPickerFragment {
+
+    private DreamBackend mBackend;
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        mBackend = DreamBackend.getInstance(context);
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DREAM;
+    }
+
+    @Override
+    protected boolean setDefaultKey(String key) {
+        Map<String, ComponentName> componentNameMap = getDreamComponentsMap();
+        if (componentNameMap.get(key) != null) {
+            mBackend.setActiveDream(componentNameMap.get(key));
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected String getDefaultKey() {
+        return mBackend.getActiveDream().flattenToString();
+    }
+
+    @Override
+    protected List<? extends CandidateInfo> getCandidates() {
+        final List<DreamCandidateInfo> candidates;
+        candidates = mBackend.getDreamInfos().stream()
+                .map(DreamCandidateInfo::new)
+                .collect(Collectors.toList());
+
+        return candidates;
+    }
+
+    @Override
+    protected void onSelectionPerformed(boolean success) {
+        super.onSelectionPerformed(success);
+
+        getActivity().finish();
+    }
+
+    private Map<String, ComponentName> getDreamComponentsMap() {
+        Map<String, ComponentName> comps = new HashMap<>();
+        mBackend.getDreamInfos()
+                .forEach((info) ->
+                        comps.put(info.componentName.flattenToString(), info.componentName));
+
+        return comps;
+    }
+
+    private static final class DreamCandidateInfo extends CandidateInfo {
+        private final CharSequence name;
+        private final Drawable icon;
+        private final String key;
+
+        DreamCandidateInfo(DreamInfo info) {
+            super(true);
+
+            name = info.caption;
+            icon = info.icon;
+            key = info.componentName.flattenToString();
+        }
+
+        @Override
+        public CharSequence loadLabel() {
+            return name;
+        }
+
+        @Override
+        public Drawable loadIcon() {
+            return icon;
+        }
+
+        @Override
+        public String getKey() {
+            return key;
+        }
+    }
+}
diff --git a/src/com/android/settings/dream/CurrentDreamPreferenceController.java b/src/com/android/settings/dream/CurrentDreamPreferenceController.java
new file mode 100644
index 0000000..5b448f8
--- /dev/null
+++ b/src/com/android/settings/dream/CurrentDreamPreferenceController.java
@@ -0,0 +1,79 @@
+/*
+ * 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.dream;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.widget.GearPreference;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.DreamInfo;
+import java.util.Optional;
+
+public class CurrentDreamPreferenceController extends PreferenceController {
+    private final DreamBackend mBackend;
+    private final static String TAG = "CurrentDreamPreferenceController";
+    private final static String CURRENT_SCREENSAVER = "current_screensaver";
+
+    public CurrentDreamPreferenceController(Context context) {
+        super(context);
+        mBackend = DreamBackend.getInstance(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mBackend.getDreamInfos().size() > 0;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return CURRENT_SCREENSAVER;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+
+        preference.setSummary(mBackend.getActiveDreamName());
+        setGearClickListenerForPreference(preference);
+    }
+
+    private void setGearClickListenerForPreference(Preference preference) {
+        if (!(preference instanceof GearPreference)) return;
+
+        GearPreference gearPreference = (GearPreference)preference;
+        Optional<DreamInfo> info = getActiveDreamInfo();
+        if (!info.isPresent() || info.get().settingsComponentName == null) {
+            gearPreference.setOnGearClickListener(null);
+            return;
+        }
+        gearPreference.setOnGearClickListener(gearPref -> launchScreenSaverSettings());
+    }
+
+    private void launchScreenSaverSettings() {
+        Optional<DreamInfo> info = getActiveDreamInfo();
+        if (!info.isPresent()) return;
+        mBackend.launchSettings(info.get());
+    }
+
+    private Optional<DreamInfo> getActiveDreamInfo() {
+        return mBackend.getDreamInfos()
+                .stream()
+                .filter((info) -> info.isActive)
+                .findFirst();
+    }
+}
diff --git a/src/com/android/settings/dream/DreamSettings.java b/src/com/android/settings/dream/DreamSettings.java
new file mode 100644
index 0000000..8cf9edb
--- /dev/null
+++ b/src/com/android/settings/dream/DreamSettings.java
@@ -0,0 +1,147 @@
+/*
+ * 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.dream;
+
+import android.provider.SearchIndexableResource;
+import android.support.annotation.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.WhenToDream;
+import java.util.ArrayList;
+import android.content.Context;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.android.settingslib.dream.DreamBackend.EITHER;
+import static com.android.settingslib.dream.DreamBackend.NEVER;
+import static com.android.settingslib.dream.DreamBackend.WHILE_CHARGING;
+import static com.android.settingslib.dream.DreamBackend.WHILE_DOCKED;
+
+public class DreamSettings extends DashboardFragment {
+
+    private static final String TAG = "DreamSettings";
+    static final String WHILE_CHARGING_ONLY = "while_charging_only";
+    static final String WHILE_DOCKED_ONLY = "while_docked_only";
+    static final String EITHER_CHARGING_OR_DOCKED = "either_charging_or_docked";
+    static final String NEVER_DREAM = "never";
+
+    @WhenToDream
+    static int getSettingFromPrefKey(String key) {
+        switch (key) {
+            case WHILE_CHARGING_ONLY:
+                return WHILE_CHARGING;
+            case WHILE_DOCKED_ONLY:
+                return WHILE_DOCKED;
+            case EITHER_CHARGING_OR_DOCKED:
+                return EITHER;
+            case NEVER_DREAM:
+            default:
+                return NEVER;
+        }
+    }
+
+    static String getKeyFromSetting(@WhenToDream int dreamSetting) {
+        switch (dreamSetting) {
+            case WHILE_CHARGING:
+                return WHILE_CHARGING_ONLY;
+            case WHILE_DOCKED:
+                return WHILE_DOCKED_ONLY;
+            case EITHER:
+                return EITHER_CHARGING_OR_DOCKED;
+            case NEVER:
+            default:
+                return NEVER_DREAM;
+        }
+    }
+
+    static int getDreamSettingDescriptionResId(@WhenToDream int dreamSetting) {
+        switch (dreamSetting) {
+            case WHILE_CHARGING:
+                return R.string.screensaver_settings_summary_sleep;
+            case WHILE_DOCKED:
+                return R.string.screensaver_settings_summary_dock;
+            case EITHER:
+                return R.string.screensaver_settings_summary_either_long;
+            case NEVER:
+            default:
+                return R.string.screensaver_settings_summary_never;
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsEvent.DREAM;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.dream_fragment_overview;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected List<PreferenceController> getPreferenceControllers(Context context) {
+        return buildPreferenceControllers(context);
+    }
+
+    public static CharSequence getSummaryTextWithDreamName(Context context) {
+        DreamBackend backend = DreamBackend.getInstance(context);
+        return getSummaryTextFromBackend(backend, context);
+    }
+
+    @VisibleForTesting
+    static CharSequence getSummaryTextFromBackend(DreamBackend backend, Context context) {
+        if (!backend.isEnabled()) {
+            return context.getString(R.string.screensaver_settings_summary_off);
+        } else {
+            return backend.getActiveDreamName();
+        }
+    }
+
+    private static List<PreferenceController> buildPreferenceControllers(Context context) {
+        List<PreferenceController> controllers = new ArrayList<>();
+        controllers.add(new CurrentDreamPreferenceController(context));
+        controllers.add(new WhenToDreamPreferenceController(context));
+        controllers.add(new StartNowPreferenceController(context));
+        return controllers;
+    }
+
+    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER
+            = new BaseSearchIndexProvider() {
+                @Override
+                public List<SearchIndexableResource> getXmlResourcesToIndex(
+                        Context context, boolean enabled) {
+                    final SearchIndexableResource sir = new SearchIndexableResource(context);
+                    sir.xmlResId = R.xml.dream_fragment_overview;
+                    return Arrays.asList(sir);
+                }
+
+                @Override
+                public List<PreferenceController> getPreferenceControllers(Context context) {
+                    return buildPreferenceControllers(context);
+                }
+            };
+}
+
diff --git a/src/com/android/settings/dream/StartNowPreferenceController.java b/src/com/android/settings/dream/StartNowPreferenceController.java
new file mode 100644
index 0000000..994b70b
--- /dev/null
+++ b/src/com/android/settings/dream/StartNowPreferenceController.java
@@ -0,0 +1,66 @@
+/*
+ * 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.dream;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.widget.Button;
+import com.android.settings.R;
+import com.android.settings.applications.LayoutPreference;
+import com.android.settings.core.PreferenceController;
+import com.android.settingslib.dream.DreamBackend;
+
+public class StartNowPreferenceController extends PreferenceController {
+    private static final String TAG = "StartNowPreferenceController";
+    private static final String PREF_KEY = "dream_start_now_button_container";
+    private final DreamBackend mBackend;
+
+    public StartNowPreferenceController(Context context) {
+        super(context);
+
+        mBackend = DreamBackend.getInstance(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return PREF_KEY;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+
+        LayoutPreference pref = (LayoutPreference) screen.findPreference(getPreferenceKey());
+        Button startButton = (Button)pref.findViewById(R.id.dream_start_now_button);
+        startButton.setOnClickListener(v -> mBackend.startDreaming());
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+
+        Button startButton = (Button)((LayoutPreference)preference)
+                .findViewById(R.id.dream_start_now_button);
+        startButton.setEnabled(mBackend.getWhenToDreamSetting() != DreamBackend.NEVER);
+    }
+}
diff --git a/src/com/android/settings/dream/WhenToDreamPicker.java b/src/com/android/settings/dream/WhenToDreamPicker.java
new file mode 100644
index 0000000..a55064d
--- /dev/null
+++ b/src/com/android/settings/dream/WhenToDreamPicker.java
@@ -0,0 +1,115 @@
+/*
+ * 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.dream;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.widget.RadioButtonPickerFragment;
+import com.android.settingslib.dream.DreamBackend;
+import java.util.ArrayList;
+import java.util.List;
+
+public class WhenToDreamPicker extends RadioButtonPickerFragment {
+
+    private static final String TAG = "WhenToDreamPicker";
+    private DreamBackend mBackend;
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        mBackend = DreamBackend.getInstance(context);
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DREAM;
+    }
+
+    @Override
+    protected List<? extends CandidateInfo> getCandidates() {
+        final String[] entries = entries();
+        final String[] values = keys();
+        final List<WhenToDreamCandidateInfo> candidates = new ArrayList<>();
+
+        if (entries == null || entries.length <= 0) return null;
+        if (values == null || values.length != entries.length) {
+            throw new IllegalArgumentException("Entries and values must be of the same length.");
+        }
+
+        for (int i = 0; i < entries.length; i++) {
+            candidates.add(new WhenToDreamCandidateInfo(entries[i], values[i]));
+        }
+
+        return candidates;
+    }
+
+    private String[] entries() {
+        return getResources().getStringArray(R.array.when_to_start_screensaver_entries);
+    }
+
+    private String[] keys() {
+        return getResources().getStringArray(R.array.when_to_start_screensaver_values);
+    }
+
+    @Override
+    protected String getDefaultKey() {
+        return DreamSettings.getKeyFromSetting(mBackend.getWhenToDreamSetting());
+    }
+
+    @Override
+    protected boolean setDefaultKey(String key) {
+        mBackend.setWhenToDream(DreamSettings.getSettingFromPrefKey(key));
+        return true;
+    }
+
+    @Override
+    protected void onSelectionPerformed(boolean success) {
+        super.onSelectionPerformed(success);
+
+        getActivity().finish();
+    }
+
+    private final class WhenToDreamCandidateInfo extends CandidateInfo {
+        private final String name;
+        private final String key;
+
+        WhenToDreamCandidateInfo(String title, String value) {
+            super(true);
+
+            name = title;
+            key = value;
+        }
+
+        @Override
+        public CharSequence loadLabel() {
+            return name;
+        }
+
+        @Override
+        public Drawable loadIcon() {
+            return null;
+        }
+
+        @Override
+        public String getKey() {
+            return key;
+        }
+    }
+}
diff --git a/src/com/android/settings/dream/WhenToDreamPreferenceController.java b/src/com/android/settings/dream/WhenToDreamPreferenceController.java
new file mode 100644
index 0000000..0d870fd
--- /dev/null
+++ b/src/com/android/settings/dream/WhenToDreamPreferenceController.java
@@ -0,0 +1,52 @@
+/*
+ * 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.dream;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import com.android.settings.core.PreferenceController;
+import com.android.settingslib.dream.DreamBackend;
+
+public class WhenToDreamPreferenceController extends PreferenceController {
+
+    private static final String WHEN_TO_START = "when_to_start";
+    private final DreamBackend mBackend;
+
+    WhenToDreamPreferenceController(Context context) {
+        super(context);
+
+        mBackend = DreamBackend.getInstance(context);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+
+        int resId = DreamSettings.getDreamSettingDescriptionResId(mBackend.getWhenToDreamSetting());
+        preference.setSummary(preference.getContext().getString(resId));
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return WHEN_TO_START;
+    }
+}
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/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index bcf830b..349a08e 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -17,9 +17,11 @@
 package com.android.settings.fuelgauge;
 
 import android.app.Activity;
+import android.app.LoaderManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.Loader;
 import android.graphics.drawable.Drawable;
 import android.os.BatteryStats;
 import android.os.Build;
@@ -59,6 +61,10 @@
 import com.android.settings.display.AutoBrightnessPreferenceController;
 import com.android.settings.display.BatteryPercentagePreferenceController;
 import com.android.settings.display.TimeoutPreferenceController;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment;
+import com.android.settings.fuelgauge.anomaly.AnomalyLoader;
+import com.android.settings.fuelgauge.anomaly.AnomalyPreferenceController;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.widget.FooterPreferenceMixin;
@@ -74,7 +80,8 @@
  * Displays a list of apps and subsystems that consume power, ordered by how much power was
  * consumed since the last time it was unplugged.
  */
-public class PowerUsageSummary extends PowerUsageBase {
+public class PowerUsageSummary extends PowerUsageBase implements
+        AnomalyDialogFragment.AnomalyDialogListener {
 
     static final String TAG = "PowerUsageSummary";
 
@@ -84,6 +91,7 @@
     private static final String KEY_BATTERY_HEADER = "battery_header";
     private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10;
     private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10;
+    private static final int ANOMALY_LOADER = 1;
 
     private static final String KEY_SCREEN_USAGE = "screen_usage";
     private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge";
@@ -117,8 +125,29 @@
 
     private LayoutPreference mBatteryLayoutPref;
     private PreferenceGroup mAppListGroup;
+    private AnomalyPreferenceController mAnomalyPreferenceController;
     private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
 
+    private LoaderManager.LoaderCallbacks<List<Anomaly>> mAnomalyLoaderCallbacks =
+            new LoaderManager.LoaderCallbacks<List<Anomaly>>() {
+
+                @Override
+                public Loader<List<Anomaly>> onCreateLoader(int id, Bundle args) {
+                    return new AnomalyLoader(getContext(), mStatsHelper);
+                }
+
+                @Override
+                public void onLoadFinished(Loader<List<Anomaly>> loader, List<Anomaly> data) {
+                    // show high usage preference if possible
+                    mAnomalyPreferenceController.updateAnomalyPreference(data);
+                }
+
+                @Override
+                public void onLoaderReset(Loader<List<Anomaly>> loader) {
+
+                }
+            };
+
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -130,6 +159,7 @@
         mLastFullChargePref = (PowerGaugePreference) findPreference(
                 KEY_TIME_SINCE_LAST_FULL_CHARGE);
         mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
+        mAnomalyPreferenceController = new AnomalyPreferenceController(this);
 
         mBatteryUtils = BatteryUtils.getInstance(getContext());
 
@@ -163,6 +193,9 @@
 
     @Override
     public boolean onPreferenceTreeClick(Preference preference) {
+        if (mAnomalyPreferenceController.onPreferenceTreeClick(preference)) {
+            return true;
+        }
         if (KEY_BATTERY_HEADER.equals(preference.getKey())) {
             performBatteryHeaderClick();
             return true;
@@ -403,6 +436,8 @@
             return;
         }
 
+        getLoaderManager().initLoader(ANOMALY_LOADER, null, mAnomalyLoaderCallbacks);
+
         cacheRemoveAllPrefs(mAppListGroup);
         mAppListGroup.setOrderingAsAdded(false);
         boolean addedSome = false;
@@ -690,6 +725,11 @@
         }
     };
 
+    @Override
+    public void onAnomalyHandled(Anomaly anomaly) {
+        mAnomalyPreferenceController.hideAnomalyPreference();
+    }
+
     private static class SummaryProvider implements SummaryLoader.SummaryProvider {
         private final Context mContext;
         private final SummaryLoader mLoader;
diff --git a/src/com/android/settings/fuelgauge/anomaly/Anomaly.java b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
new file mode 100644
index 0000000..a10d3f4
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
@@ -0,0 +1,136 @@
+/*
+ * 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.fuelgauge.anomaly;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Data that represents an app has been detected as anomaly. It contains
+ *
+ * 1. Basic information of the app(i.e. uid, package name)
+ * 2. Type of anomaly
+ * 3. Data that has been detected as anomaly(i.e wakelock time)
+ */
+public class Anomaly implements Parcelable {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({AnomalyType.WAKE_LOCK})
+    public @interface AnomalyType {
+        int WAKE_LOCK = 0;
+    }
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({AnomalyActionType.FORCE_STOP})
+    public @interface AnomalyActionType {
+        int FORCE_STOP = 0;
+    }
+
+    /**
+     * Type of this this anomaly
+     */
+    public final int type;
+    public final int uid;
+    public final long wakelockTimeMs;
+
+    /**
+     * Display name of this anomaly, usually it is the app name
+     */
+    public final String displayName;
+    public final String packageName;
+
+    private Anomaly(Builder builder) {
+        type = builder.mType;
+        uid = builder.mUid;
+        displayName = builder.mDisplayName;
+        packageName = builder.mPackageName;
+        wakelockTimeMs = builder.mWakeLockTimeMs;
+    }
+
+    private Anomaly(Parcel in) {
+        type = in.readInt();
+        uid = in.readInt();
+        displayName = in.readString();
+        packageName = in.readString();
+        wakelockTimeMs = in.readLong();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(type);
+        dest.writeInt(uid);
+        dest.writeString(displayName);
+        dest.writeString(packageName);
+        dest.writeLong(wakelockTimeMs);
+    }
+
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public Anomaly createFromParcel(Parcel in) {
+            return new Anomaly(in);
+        }
+
+        public Anomaly[] newArray(int size) {
+            return new Anomaly[size];
+        }
+    };
+
+    public static final class Builder {
+        @AnomalyType
+        private int mType;
+        private int mUid;
+        private String mDisplayName;
+        private String mPackageName;
+        private long mWakeLockTimeMs;
+
+        public Builder setType(@AnomalyType int type) {
+            mType = type;
+            return this;
+        }
+
+        public Builder setUid(int uid) {
+            mUid = uid;
+            return this;
+        }
+
+        public Builder setDisplayName(String displayName) {
+            mDisplayName = displayName;
+            return this;
+        }
+
+        public Builder setPackageName(String packageName) {
+            mPackageName = packageName;
+            return this;
+        }
+
+        public Builder setWakeLockTimeMs(long wakeLockTimeMs) {
+            mWakeLockTimeMs = wakeLockTimeMs;
+            return this;
+        }
+
+        public Anomaly build() {
+            return new Anomaly(this);
+        }
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java
new file mode 100644
index 0000000..122b478
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java
@@ -0,0 +1,103 @@
+/*
+ * 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.fuelgauge.anomaly;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.fuelgauge.anomaly.action.AnomalyAction;
+
+/**
+ * Dialog Fragment to show action dialog for each anomaly
+ */
+public class AnomalyDialogFragment extends InstrumentedDialogFragment implements
+        DialogInterface.OnClickListener {
+
+    private static final String ARG_ANOMALY = "anomaly";
+
+    @VisibleForTesting
+    Anomaly mAnomaly;
+
+    /**
+     * Listener to give the control back to target fragment
+     */
+    public interface AnomalyDialogListener {
+        /**
+         * This method is invoked once anomaly is handled, then target fragment could do
+         * extra work. One example is that fragment could remove the anomaly preference
+         * since it has been handled
+         *
+         * @param anomaly that has been handled
+         */
+        void onAnomalyHandled(Anomaly anomaly);
+    }
+
+    public static AnomalyDialogFragment newInstance(Anomaly anomaly) {
+        AnomalyDialogFragment dialogFragment = new AnomalyDialogFragment();
+
+        Bundle args = new Bundle(1);
+        args.putParcelable(ARG_ANOMALY, anomaly);
+        dialogFragment.setArguments(args);
+
+        return dialogFragment;
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        // TODO(b/37681923): add anomaly metric id
+        return 0;
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        final AnomalyDialogListener lsn = (AnomalyDialogListener) getTargetFragment();
+        if (lsn == null) {
+            return;
+        }
+
+        final AnomalyAction anomalyAction = AnomalyUtils.getAnomalyAction(mAnomaly.type);
+        anomalyAction.handlePositiveAction(mAnomaly.packageName);
+        lsn.onAnomalyHandled(mAnomaly);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final Bundle bundle = getArguments();
+        mAnomaly = bundle.getParcelable(ARG_ANOMALY);
+
+        final Context context = getContext();
+        final AnomalyAction anomalyAction = AnomalyUtils.getAnomalyAction(mAnomaly.type);
+        switch (anomalyAction.getActionType()) {
+            case Anomaly.AnomalyActionType.FORCE_STOP:
+                return new AlertDialog.Builder(context)
+                        .setTitle(R.string.force_stop_dlg_title)
+                        .setMessage(R.string.force_stop_dlg_text)
+                        .setPositiveButton(R.string.dlg_ok, this)
+                        .setNegativeButton(R.string.dlg_cancel, null)
+                        .create();
+            default:
+                throw new IllegalArgumentException("unknown type " + mAnomaly.type);
+        }
+    }
+
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
new file mode 100644
index 0000000..e689256a
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
@@ -0,0 +1,51 @@
+/*
+ * 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.fuelgauge.anomaly;
+
+import android.content.Context;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector;
+import com.android.settings.utils.AsyncLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loader to compute which apps are anomaly and return a anomaly list. It will return
+ * an empty list if there is no anomaly.
+ */
+//TODO(b/36924669): add test for this file, for now it seems there is nothing to test
+public class AnomalyLoader extends AsyncLoader<List<Anomaly>> {
+    private BatteryStatsHelper mBatteryStatsHelper;
+
+    public AnomalyLoader(Context context, BatteryStatsHelper batteryStatsHelper) {
+        super(context);
+        mBatteryStatsHelper = batteryStatsHelper;
+    }
+
+    @Override
+    protected void onDiscardResult(List<Anomaly> result) {}
+
+    @Override
+    public List<Anomaly> loadInBackground() {
+        final List<Anomaly> anomalies = new ArrayList<>();
+        anomalies.addAll(new WakeLockAnomalyDetector().detectAnomalies(mBatteryStatsHelper));
+
+        return anomalies;
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceController.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceController.java
new file mode 100644
index 0000000..b499690
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceController.java
@@ -0,0 +1,76 @@
+/*
+ * 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.fuelgauge.anomaly;
+
+import android.support.annotation.VisibleForTesting;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+
+import java.util.List;
+
+/**
+ * Manager that responsible for updating anomaly preference and handling preference click.
+ */
+public class AnomalyPreferenceController {
+    private static final String TAG = "AnomalyPreferenceController";
+    @VisibleForTesting
+    static final String ANOMALY_KEY = "high_usage";
+    private static final int REQUEST_ANOMALY_ACTION = 0;
+    private PreferenceFragment mFragment;
+    @VisibleForTesting
+    Preference mAnomalyPreference;
+    @VisibleForTesting
+    List<Anomaly> mAnomalies;
+
+    public AnomalyPreferenceController(PreferenceFragment fragment) {
+        mFragment = fragment;
+        mAnomalyPreference = mFragment.getPreferenceScreen().findPreference(ANOMALY_KEY);
+        hideAnomalyPreference();
+    }
+
+    public boolean onPreferenceTreeClick(Preference preference) {
+        if (mAnomalies != null && ANOMALY_KEY.equals(preference.getKey())) {
+            if (mAnomalies.size() == 1) {
+                final Anomaly anomaly = mAnomalies.get(0);
+                AnomalyDialogFragment dialogFragment = AnomalyDialogFragment.newInstance(anomaly);
+                dialogFragment.setTargetFragment(mFragment, REQUEST_ANOMALY_ACTION);
+                dialogFragment.show(mFragment.getFragmentManager(), TAG);
+            } else {
+                //TODO(b/37681665): start a new fragment to handle it
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Update anomaly preference based on {@code anomalies}, also store a reference
+     * of {@paramref anomalies}, which would be used in {@link #onPreferenceTreeClick(Preference)}
+     *
+     * @param anomalies used to update the summary, this method will store a reference of it
+     */
+    public void updateAnomalyPreference(List<Anomaly> anomalies) {
+        mAnomalies = anomalies;
+
+        mAnomalyPreference.setVisible(true);
+        //TODO(b/36924669): update summary for anomaly preference
+    }
+
+    public void hideAnomalyPreference() {
+        mAnomalyPreference.setVisible(false);
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
new file mode 100644
index 0000000..987482e
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
@@ -0,0 +1,40 @@
+/*
+ * 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.fuelgauge.anomaly;
+
+import com.android.settings.fuelgauge.anomaly.action.AnomalyAction;
+import com.android.settings.fuelgauge.anomaly.action.ForceStopAction;
+
+/**
+ * Utitily class for anomaly detection
+ */
+public class AnomalyUtils {
+
+    /**
+     * Return the corresponding {@link AnomalyAction} according to {@link AnomalyType}
+     *
+     * @return corresponding {@link AnomalyAction}, or null if cannot find it.
+     */
+    public static final AnomalyAction getAnomalyAction(int anomalyType) {
+        switch (anomalyType) {
+            case Anomaly.AnomalyType.WAKE_LOCK:
+                return new ForceStopAction();
+            default:
+                return null;
+        }
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java b/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java
new file mode 100644
index 0000000..8013797
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java
@@ -0,0 +1,25 @@
+/*
+ * 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.fuelgauge.anomaly.action;
+
+/**
+ * Interface for anomaly action, which is triggered if we need to handle the anomaly
+ */
+public interface AnomalyAction {
+    void handlePositiveAction(String packageName);
+    int getActionType();
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/action/ForceStopAction.java b/src/com/android/settings/fuelgauge/anomaly/action/ForceStopAction.java
new file mode 100644
index 0000000..be5d6ab
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/action/ForceStopAction.java
@@ -0,0 +1,34 @@
+/*
+ * 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.fuelgauge.anomaly.action;
+
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+
+/**
+ * Force stop action for anomaly app, which means to stop the app which causes anomaly
+ */
+public class ForceStopAction implements AnomalyAction {
+    @Override
+    public void handlePositiveAction(String packageName) {
+        // force stop the package
+    }
+
+    @Override
+    public int getActionType() {
+        return Anomaly.AnomalyActionType.FORCE_STOP;
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/AnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/AnomalyDetector.java
new file mode 100644
index 0000000..b04ea66
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/AnomalyDetector.java
@@ -0,0 +1,27 @@
+/*
+ * 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.fuelgauge.anomaly.checker;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+
+import java.util.List;
+
+public interface AnomalyDetector {
+    List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper);
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
new file mode 100644
index 0000000..a9ebc66
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
@@ -0,0 +1,36 @@
+/*
+ * 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.fuelgauge.anomaly.checker;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Check whether apps holding wakelock too long
+ */
+public class WakeLockAnomalyDetector implements AnomalyDetector {
+
+    @Override
+    public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
+        //TODO(b/36921529): check anomaly using the batteryStatsHelper
+        final List<Anomaly> anomalies = new ArrayList<>();
+        return anomalies;
+    }
+}
diff --git a/src/com/android/settings/gestures/SwipeToNotificationPreferenceController.java b/src/com/android/settings/gestures/SwipeToNotificationPreferenceController.java
index 22b88fc..89d38a1 100644
--- a/src/com/android/settings/gestures/SwipeToNotificationPreferenceController.java
+++ b/src/com/android/settings/gestures/SwipeToNotificationPreferenceController.java
@@ -60,7 +60,7 @@
     @Override
     protected boolean isSwitchPrefEnabled() {
         return Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, 0)
+                Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, 1)
                 == 1;
     }
 }
diff --git a/src/com/android/settings/overlay/SupportFeatureProvider.java b/src/com/android/settings/overlay/SupportFeatureProvider.java
index 0f8d424..1831486 100644
--- a/src/com/android/settings/overlay/SupportFeatureProvider.java
+++ b/src/com/android/settings/overlay/SupportFeatureProvider.java
@@ -129,6 +129,19 @@
     void startSupport(Activity activity, Account account, @SupportType int type);
 
     /**
+     * Starts support v2, invokes the support home page. Will no-op if support v2 is not enabled.
+     *
+     * @param activity Calling activity.
+     */
+    void startSupportV2(Activity activity);
+
+    /**
+     * Checks if support v2 is enabled for this device.
+     * @return a boolean indicating if support v2 is enabled.
+     */
+    boolean isSupportV2Enabled();
+
+    /**
      * Returns an {@link Intent} that opens help and allow user get help on sign in.
      */
     Intent getSignInHelpIntent(Context context);
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index 63e8a7a..75ac350 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -47,6 +47,7 @@
 import com.android.settings.deviceinfo.StorageDashboardFragment;
 import com.android.settings.deviceinfo.StorageSettings;
 import com.android.settings.display.ScreenZoomSettings;
+import com.android.settings.dream.DreamSettings;
 import com.android.settings.enterprise.EnterprisePrivacySettings;
 import com.android.settings.fuelgauge.BatterySaverSettings;
 import com.android.settings.fuelgauge.PowerUsageAdvanced;
@@ -185,6 +186,7 @@
                 R.drawable.ic_settings_accessibility);
         addIndex(ChannelImportanceSettings.class, NO_DATA_RES_ID,
                 R.drawable.ic_settings_notifications);
+        addIndex(DreamSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_display);
     }
 
     private SearchIndexableResources() {
diff --git a/src/com/android/settings/vpn2/LegacyVpnPreference.java b/src/com/android/settings/vpn2/LegacyVpnPreference.java
index a4d7221..4ef2808 100644
--- a/src/com/android/settings/vpn2/LegacyVpnPreference.java
+++ b/src/com/android/settings/vpn2/LegacyVpnPreference.java
@@ -34,7 +34,7 @@
 
     LegacyVpnPreference(Context context) {
         super(context, null /* attrs */);
-        setIcon(R.drawable.ic_settings_24dp);
+        setIcon(R.drawable.ic_vpn_key);
     }
 
     public VpnProfile getProfile() {
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/applications/AppPermissionsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/AppPermissionsPreferenceControllerTest.java
new file mode 100644
index 0000000..6e3cc4b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/AppPermissionsPreferenceControllerTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+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 android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+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.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AppPermissionsPreferenceControllerTest {
+
+    private static final String PERM_LOCATION = "android.permission-group.LOCATION";
+    private static final String PERM_MICROPHONE = "android.permission-group.MICROPHONE";
+    private static final String PERM_CAMERA = "android.permission-group.CAMERA";
+    private static final String PERM_SMS = "android.permission-group.SMS";
+    private static final String PERM_CONTACTS = "android.permission-group.CONTACTS";
+    private static final String PERM_PHONE = "android.permission-group.PHONE";
+    private static final String LABEL_LOCATION = "Location";
+    private static final String LABEL_MICROPHONE = "Microphone";
+    private static final String LABEL_CAMERA = "Camera";
+    private static final String LABEL_SMS = "Sms";
+    private static final String LABEL_CONTACTS = "Contacts";
+    private static final String LABEL_PHONE = "Phone";
+
+    @Mock
+    private Preference mPreference;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private PermissionGroupInfo mGroupLocation;
+    @Mock
+    private PermissionGroupInfo mGroupMic;
+    @Mock
+    private PermissionGroupInfo mGroupCamera;
+    @Mock
+    private PermissionGroupInfo mGroupSms;
+    @Mock
+    private PermissionGroupInfo mGroupContacts;
+    @Mock
+    private PermissionGroupInfo mGroupPhone;
+
+    private Context mContext;
+    private AppPermissionsPreferenceController mController;
+    private PermissionInfo mPermLocation;
+    private PermissionInfo mPermMic;
+    private PermissionInfo mPermCamera;
+    private PermissionInfo mPermSms;
+    private PermissionInfo mPermContacts;
+    private PermissionInfo mPermPhone;
+
+    @Before
+    public void setUp() throws NameNotFoundException {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+
+        // create permission groups
+        when(mPackageManager.getPermissionGroupInfo(eq(PERM_LOCATION), anyInt()))
+            .thenReturn(mGroupLocation);
+        when(mGroupLocation.loadLabel(mPackageManager)).thenReturn(LABEL_LOCATION);
+        when(mPackageManager.getPermissionGroupInfo(eq(PERM_MICROPHONE), anyInt()))
+            .thenReturn(mGroupMic);
+        when(mGroupMic.loadLabel(mPackageManager)).thenReturn(LABEL_MICROPHONE);
+        when(mPackageManager.getPermissionGroupInfo(eq(PERM_CAMERA), anyInt()))
+            .thenReturn(mGroupCamera);
+        when(mGroupCamera.loadLabel(mPackageManager)).thenReturn(LABEL_CAMERA);
+        when(mPackageManager.getPermissionGroupInfo(eq(PERM_SMS), anyInt())).thenReturn(mGroupSms);
+        when(mGroupSms.loadLabel(mPackageManager)).thenReturn(LABEL_SMS);
+        when(mPackageManager.getPermissionGroupInfo(eq(PERM_CONTACTS), anyInt()))
+            .thenReturn(mGroupContacts);
+        when(mGroupContacts.loadLabel(mPackageManager)).thenReturn(LABEL_CONTACTS);
+        when(mPackageManager.getPermissionGroupInfo(eq(PERM_PHONE), anyInt()))
+            .thenReturn(mGroupPhone);
+        when(mGroupPhone.loadLabel(mPackageManager)).thenReturn(LABEL_PHONE);
+
+        // create permissions
+        mPermLocation = new PermissionInfo();
+        mPermLocation.name = "Permission1";
+        mPermLocation.group = PERM_LOCATION;
+        mPermMic = new PermissionInfo();
+        mPermMic.name = "Permission2";
+        mPermMic.group = PERM_MICROPHONE;
+        mPermCamera = new PermissionInfo();
+        mPermCamera.name = "Permission3";
+        mPermCamera.group = PERM_CAMERA;
+        mPermSms = new PermissionInfo();
+        mPermSms.name = "Permission4";
+        mPermSms.group = PERM_SMS;
+        mPermContacts = new PermissionInfo();
+        mPermContacts.name = "Permission4";
+        mPermContacts.group = PERM_CONTACTS;
+        mPermPhone = new PermissionInfo();
+        mPermPhone.name = "Permission4";
+        mPermPhone.group = PERM_PHONE;
+        final List<PermissionInfo> permissions = new ArrayList<>();
+        permissions.add(mPermLocation);
+        permissions.add(mPermMic);
+        permissions.add(mPermCamera);
+        permissions.add(mPermSms);
+        permissions.add(mPermContacts);
+        permissions.add(mPermPhone);
+        when(mPackageManager.queryPermissionsByGroup(anyString(), anyInt()))
+            .thenReturn(permissions);
+
+        mController = spy(new AppPermissionsPreferenceController(mContext));
+    }
+
+    @Test
+    public void isAvailable_shouldAlwaysReturnTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void updateState_noGrantedPermissions_shouldNotSetSummary() {
+        final List<PackageInfo> installedPackages = new ArrayList<>();
+        final PackageInfo info = new PackageInfo();
+        installedPackages.add(info);
+        when(mPackageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS))
+            .thenReturn(installedPackages);
+
+        mController.updateState(mPreference);
+
+        verify(mPreference, never()).setSummary(anyString());
+    }
+
+    @Test
+    public void updateState_hasPermissions_shouldSetSummary() {
+        final List<PackageInfo> installedPackages = new ArrayList<>();
+        final PackageInfo info = new PackageInfo();
+        installedPackages.add(info);
+        when(mPackageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS))
+            .thenReturn(installedPackages);
+        PermissionInfo[] permissions = new PermissionInfo[4];
+        info.permissions = permissions;
+
+        permissions[0] = mPermLocation;
+        permissions[1] = mPermMic;
+        permissions[2] = mPermCamera;
+        permissions[3] = mPermSms;
+        mController.updateState(mPreference);
+        verify(mPreference).setSummary("Apps using Location, Microphone, Camera");
+
+        permissions[0] = mPermPhone;
+        permissions[1] = mPermMic;
+        permissions[2] = mPermCamera;
+        permissions[3] = mPermSms;
+        mController.updateState(mPreference);
+        verify(mPreference).setSummary("Apps using Microphone, Camera, Sms");
+
+        permissions[0] = mPermPhone;
+        permissions[1] = mPermMic;
+        permissions[2] = mPermContacts;
+        permissions[3] = mPermSms;
+        mController.updateState(mPreference);
+        verify(mPreference).setSummary("Apps using Microphone, Sms, Contacts");
+
+        permissions = new PermissionInfo[2];
+        info.permissions = permissions;
+        permissions[0] = mPermLocation;
+        permissions[1] = mPermCamera;
+        mController.updateState(mPreference);
+        verify(mPreference).setSummary("Apps using Location, Camera");
+
+        permissions = new PermissionInfo[1];
+        info.permissions = permissions;
+        permissions[0] = mPermCamera;
+        mController.updateState(mPreference);
+        verify(mPreference).setSummary("Apps using Camera");
+    }
+}
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/dream/CurrentDreamPickerTest.java b/tests/robotests/src/com/android/settings/dream/CurrentDreamPickerTest.java
new file mode 100644
index 0000000..24082b6
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/CurrentDreamPickerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.dream;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.UserManager;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.DreamInfo;
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class CurrentDreamPickerTest {
+    private static String COMPONENT_KEY = "mocked_component_name_string";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private DreamBackend mBackend;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private UserManager mUserManager;
+    private CurrentDreamPicker mPicker;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        FakeFeatureFactory.setupForTest(mActivity);
+
+        mPicker = new CurrentDreamPicker();
+        mPicker.onAttach((Context)mActivity);
+
+        ReflectionHelpers.setField(mPicker, "mBackend", mBackend);
+    }
+
+    @Test
+    public void getDefaultShouldReturnActiveDream() {
+        ComponentName mockComponentName = mock(ComponentName.class);
+        when(mockComponentName.flattenToString()).thenReturn(COMPONENT_KEY);
+        when(mBackend.getActiveDream()).thenReturn(mockComponentName);
+
+        assertThat(mPicker.getDefaultKey()).isEqualTo(COMPONENT_KEY);
+    }
+
+    @Test
+    public void setDefaultShouldUpdateActiveDream() {
+        DreamInfo mockInfo = mock(DreamInfo.class);
+        ComponentName mockName = mock(ComponentName.class);
+
+        mockInfo.componentName = mockName;
+        when(mockName.flattenToString()).thenReturn(COMPONENT_KEY);
+        when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(Arrays.asList(mockInfo)));
+
+        mPicker.setDefaultKey(COMPONENT_KEY);
+
+        verify(mBackend).setActiveDream(mockName);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/CurrentDreamPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/CurrentDreamPreferenceControllerTest.java
new file mode 100644
index 0000000..c55f080
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/CurrentDreamPreferenceControllerTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.dream;
+
+import android.content.ComponentName;
+import android.content.Context;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.widget.GearPreference;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.DreamInfo;
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class CurrentDreamPreferenceControllerTest {
+    private static String TAG = "CurrentDreamPreferenceControllerTest";
+
+    private CurrentDreamPreferenceController mController;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private DreamBackend mBackend;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private DreamInfo mDreamInfo;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mController = new CurrentDreamPreferenceController(mContext);
+        ReflectionHelpers.setField(mController, "mBackend", mBackend);
+    }
+
+    @Test
+    public void isDisabledIfNoDreamsAvailable() {
+        when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(0));
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isEnabledIfDreamsAvailable() {
+        when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(Arrays.asList(mDreamInfo)));
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void gearShowsIfActiveDreamInfoHasOptions() {
+        mDreamInfo.settingsComponentName = mock(ComponentName.class);
+        mDreamInfo.isActive = true;
+
+        when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(Arrays.asList(mDreamInfo)));
+
+        GearPreference mockPref = mock(GearPreference.class);
+        ArgumentCaptor<GearPreference.OnGearClickListener> captor =
+                ArgumentCaptor.forClass(GearPreference.OnGearClickListener.class);
+
+        // verify that updateState sets a non-null gear click listener
+        mController.updateState(mockPref);
+        verify(mockPref).setOnGearClickListener(captor.capture());
+        captor.getAllValues().forEach(listener -> assertThat(listener).isNotNull());
+    }
+
+    @Test
+    public void gearHidesIfActiveDreamInfoHasNoOptions() {
+        mDreamInfo.settingsComponentName = null;
+        mDreamInfo.isActive = true;
+
+        when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(Arrays.asList(mDreamInfo)));
+
+        GearPreference mockPref = mock(GearPreference.class);
+        ArgumentCaptor<GearPreference.OnGearClickListener> captor =
+                ArgumentCaptor.forClass(GearPreference.OnGearClickListener.class);
+
+        // setting a null onGearClickListener removes the gear from view
+        mController.updateState(mockPref);
+        verify(mockPref).setOnGearClickListener(captor.capture());
+        captor.getAllValues().forEach(listener -> assertThat(listener).isNull());
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/DreamSettingsTest.java b/tests/robotests/src/com/android/settings/dream/DreamSettingsTest.java
new file mode 100644
index 0000000..95f57b3
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/DreamSettingsTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.dream;
+
+import android.content.Context;
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.WhenToDream;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DreamSettingsTest {
+    private static final List<String> KEYS = Arrays.asList(
+            DreamSettings.WHILE_CHARGING_ONLY,
+            DreamSettings.WHILE_DOCKED_ONLY,
+            DreamSettings.EITHER_CHARGING_OR_DOCKED,
+            DreamSettings.NEVER_DREAM
+        );
+
+    private static final @WhenToDream int[] SETTINGS = {
+                DreamBackend.WHILE_CHARGING,
+                DreamBackend.WHILE_DOCKED,
+                DreamBackend.EITHER,
+                DreamBackend.NEVER,
+        };
+
+    private static final int[] RES_IDS = {
+            R.string.screensaver_settings_summary_sleep,
+            R.string.screensaver_settings_summary_dock,
+            R.string.screensaver_settings_summary_either_long,
+            R.string.screensaver_settings_summary_never
+    };
+
+    @Test
+    public void getSettingFromPrefKey() {
+        for (int i = 0; i < KEYS.size(); i++) {
+            assertThat(DreamSettings.getSettingFromPrefKey(KEYS.get(i)))
+                    .isEqualTo(SETTINGS[i]);
+        }
+        // Default case
+        assertThat(DreamSettings.getSettingFromPrefKey("garbage value"))
+                .isEqualTo(DreamBackend.NEVER);
+    }
+
+    @Test
+    public void getKeyFromSetting() {
+        for (int i = 0; i < SETTINGS.length; i++) {
+            assertThat(DreamSettings.getKeyFromSetting(SETTINGS[i]))
+                    .isEqualTo(KEYS.get(i));
+        }
+        // Default
+        assertThat(DreamSettings.getKeyFromSetting(-1))
+                .isEqualTo(DreamSettings.NEVER_DREAM);
+    }
+
+    @Test
+    public void getDreamSettingDescriptionResId() {
+        for (int i = 0; i < SETTINGS.length; i++) {
+            assertThat(DreamSettings.getDreamSettingDescriptionResId(SETTINGS[i]))
+                    .isEqualTo(RES_IDS[i]);
+        }
+        // Default
+        assertThat(DreamSettings.getDreamSettingDescriptionResId(-1))
+                .isEqualTo(R.string.screensaver_settings_summary_never);
+    }
+
+    @Test
+    public void summaryText_whenDreamsAreOff() {
+        DreamBackend mockBackend = mock(DreamBackend.class);
+        Context mockContext = mock(Context.class);
+        when(mockBackend.isEnabled()).thenReturn(false);
+
+        assertThat(DreamSettings.getSummaryTextFromBackend(mockBackend, mockContext))
+                .isEqualTo(mockContext.getString(R.string.screensaver_settings_summary_off));
+    }
+
+    @Test
+    public void summaryTest_WhenDreamsAreOn() {
+        final String fakeName = "test_name";
+        DreamBackend mockBackend = mock(DreamBackend.class);
+        Context mockContext = mock(Context.class);
+        when(mockBackend.isEnabled()).thenReturn(true);
+        when(mockBackend.getActiveDreamName()).thenReturn(fakeName);
+
+        assertThat(DreamSettings.getSummaryTextFromBackend(mockBackend, mockContext))
+                .isEqualTo(fakeName);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/StartNowPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/StartNowPreferenceControllerTest.java
new file mode 100644
index 0000000..5cbd610
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/StartNowPreferenceControllerTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.dream;
+
+import android.content.Context;
+import android.support.v7.preference.PreferenceScreen;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.LayoutPreference;
+import com.android.settingslib.dream.DreamBackend;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class StartNowPreferenceControllerTest {
+    private StartNowPreferenceController mController;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private LayoutPreference mLayoutPref;
+    @Mock
+    private Button mButton;
+    @Mock
+    private DreamBackend mBackend;
+
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mController = new StartNowPreferenceController(mContext);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mLayoutPref);
+        when(mLayoutPref.findViewById(R.id.dream_start_now_button)).thenReturn(mButton);
+
+        ReflectionHelpers.setField(mController, "mBackend", mBackend);
+    }
+
+    @Test
+    public void setsOnClickListenerForStartNow() {
+        ArgumentCaptor<OnClickListener> captor =
+                ArgumentCaptor.forClass(Button.OnClickListener.class);
+
+        mController.displayPreference(mScreen);
+        verify(mButton).setOnClickListener(captor.capture());
+        assertThat(captor.getValue()).isNotNull();
+    }
+
+    @Test
+    public void buttonIsDisabledWhenNeverDreaming() {
+        when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.NEVER);
+
+        mController.updateState(mLayoutPref);
+        verify(mButton).setEnabled(false);
+    }
+
+    @Test
+    public void buttonIsEnabledWhenDreamIsAvailable() {
+        when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.EITHER);
+
+        mController.updateState(mLayoutPref);
+        verify(mButton).setEnabled(true);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/WhenToDreamPickerTest.java b/tests/robotests/src/com/android/settings/dream/WhenToDreamPickerTest.java
new file mode 100644
index 0000000..2571e1b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/WhenToDreamPickerTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.dream;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.dream.DreamBackend;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WhenToDreamPickerTest {
+    private WhenToDreamPicker mPicker;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private DreamBackend mBackend;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private UserManager mUserManager;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        FakeFeatureFactory.setupForTest(mActivity);
+
+        mPicker = new WhenToDreamPicker();
+        mPicker.onAttach((Context)mActivity);
+
+        ReflectionHelpers.setField(mPicker, "mBackend", mBackend);
+    }
+
+    @Test
+    public void getDefaultKeyReturnsCurrentWhenToDreamSetting() {
+        when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.WHILE_CHARGING);
+        assertThat(mPicker.getDefaultKey())
+                .isEqualTo(DreamSettings.getKeyFromSetting(DreamBackend.WHILE_CHARGING));
+
+        when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.WHILE_DOCKED);
+        assertThat(mPicker.getDefaultKey())
+                .isEqualTo(DreamSettings.getKeyFromSetting(DreamBackend.WHILE_DOCKED));
+
+        when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.EITHER);
+        assertThat(mPicker.getDefaultKey())
+                .isEqualTo(DreamSettings.getKeyFromSetting(DreamBackend.EITHER));
+
+        when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.NEVER);
+        assertThat(mPicker.getDefaultKey())
+                .isEqualTo(DreamSettings.getKeyFromSetting(DreamBackend.NEVER));
+    }
+
+    @Test
+    public void setDreamWhileCharging() {
+        String key = DreamSettings.getKeyFromSetting(DreamBackend.WHILE_CHARGING);
+        mPicker.setDefaultKey(key);
+        verify(mBackend).setWhenToDream(DreamBackend.WHILE_CHARGING);
+    }
+
+    @Test
+    public void setDreamWhileDocked() {
+        String key = DreamSettings.getKeyFromSetting(DreamBackend.WHILE_DOCKED);
+        mPicker.setDefaultKey(key);
+        verify(mBackend).setWhenToDream(DreamBackend.WHILE_DOCKED);
+    }
+
+    @Test
+    public void setDreamWhileChargingOrDocked() {
+        String key = DreamSettings.getKeyFromSetting(DreamBackend.EITHER);
+        mPicker.setDefaultKey(key);
+        verify(mBackend).setWhenToDream(DreamBackend.EITHER);
+    }
+
+    @Test
+    public void setDreamNever() {
+        String key = DreamSettings.getKeyFromSetting(DreamBackend.NEVER);
+        mPicker.setDefaultKey(key);
+        verify(mBackend).setWhenToDream(DreamBackend.NEVER);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
new file mode 100644
index 0000000..0eded7b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.dream;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.WhenToDream;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WhenToDreamPreferenceControllerTest {
+    private WhenToDreamPreferenceController mController;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private DreamBackend mBackend;
+
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mController = new WhenToDreamPreferenceController(mContext);
+        ReflectionHelpers.setField(mController, "mBackend", mBackend);
+    }
+
+    @Test
+    public void updateSummary() {
+        // Don't have to test the other settings because DreamSettings tests that all
+        // @WhenToDream values map to the correct ResId
+        final @WhenToDream int testSetting = DreamBackend.WHILE_CHARGING;
+        final Preference mockPref = mock(Preference.class);
+        when(mockPref.getContext()).thenReturn(mContext);
+        when(mBackend.getWhenToDreamSetting()).thenReturn(testSetting);
+        final String expectedString =
+                mContext.getString(DreamSettings.getDreamSettingDescriptionResId(testSetting));
+
+        mController.updateState(mockPref);
+        verify(mockPref).setSummary(expectedString);
+    }
+}
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/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java
new file mode 100644
index 0000000..95a8ff3
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.fuelgauge.anomaly;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowDialog;
+import org.robolectric.util.FragmentTestUtil;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyDialogFragmentTest {
+    @Anomaly.AnomalyType
+    private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK;
+    private static final String PACKAGE_NAME = "com.android.app";
+    private static final int UID = 111;
+    private Anomaly mAnomaly;
+    private AnomalyDialogFragment mAnomalyDialogFragment;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mAnomaly = new Anomaly.Builder()
+                .setType(ANOMALY_TYPE)
+                .setUid(UID)
+                .setPackageName(PACKAGE_NAME)
+                .build();
+
+        mAnomalyDialogFragment = AnomalyDialogFragment.newInstance(mAnomaly);
+    }
+
+    @Test
+    public void testOnCreateDialog_hasCorrectData() {
+        FragmentTestUtil.startFragment(mAnomalyDialogFragment);
+
+        assertThat(mAnomalyDialogFragment.mAnomaly).isEqualTo(mAnomaly);
+    }
+
+    @Test
+    public void testOnCreateDialog_hasCorrectDialog() {
+        FragmentTestUtil.startFragment(mAnomalyDialogFragment);
+
+        final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog();
+        ShadowAlertDialog shadowDialog = shadowOf(dialog);
+
+        assertThat(shadowDialog.getMessage()).isEqualTo(
+                mContext.getString(R.string.force_stop_dlg_text));
+        assertThat(shadowDialog.getTitle()).isEqualTo(
+                mContext.getString(R.string.force_stop_dlg_title));
+        assertThat(dialog.getButton(DialogInterface.BUTTON_POSITIVE).getText()).isEqualTo(
+                mContext.getString(R.string.dlg_ok));
+        assertThat(dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText()).isEqualTo(
+                mContext.getString(R.string.dlg_cancel));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceControllerTest.java
new file mode 100644
index 0000000..f2e4482
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceControllerTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.anomaly;
+
+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.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.Settings;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.fuelgauge.ButtonActionDialogFragmentTest;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
+import com.android.settings.testutils.shadow.ShadowEventLogWriter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowDialog;
+import org.robolectric.util.FragmentTestUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyPreferenceControllerTest {
+    @Anomaly.AnomalyType
+    private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK;
+    private static final String PACKAGE_NAME = "com.android.app";
+    private static final int UID = 111;
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private PreferenceFragment mFragment;
+    @Mock
+    private FragmentManager mFragmentManager;
+    @Mock
+    private FragmentTransaction mFragmentTransaction;
+    private AnomalyPreferenceController mAnomalyPreferenceController;
+    private Preference mPreference;
+    private Context mContext;
+    private List<Anomaly> mAnomalyList;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mPreference.setKey(AnomalyPreferenceController.ANOMALY_KEY);
+        when(mFragment.getPreferenceManager().findPreference(any())).thenReturn(mPreference);
+        when(mFragment.getFragmentManager()).thenReturn(mFragmentManager);
+        when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
+
+        mAnomalyList = new ArrayList<>();
+        Anomaly anomaly = new Anomaly.Builder()
+                .setType(ANOMALY_TYPE)
+                .setUid(UID)
+                .setPackageName(PACKAGE_NAME)
+                .build();
+        mAnomalyList.add(anomaly);
+
+        mAnomalyPreferenceController = new AnomalyPreferenceController(mFragment);
+    }
+
+    @Test
+    public void testUpdateAnomalyPreference_hasCorrectData() {
+        mAnomalyPreferenceController.updateAnomalyPreference(mAnomalyList);
+
+        //add more test when this method is complete
+        assertThat(mAnomalyPreferenceController.mAnomalies).isEqualTo(mAnomalyList);
+    }
+
+    @Test
+    public void testOnPreferenceTreeClick_oneAnomaly_showDialog() {
+        mAnomalyPreferenceController.mAnomalies = mAnomalyList;
+
+        mAnomalyPreferenceController.onPreferenceTreeClick(mPreference);
+
+        verify(mFragmentManager).beginTransaction();
+        verify(mFragmentTransaction).add(any(), anyString());
+        verify(mFragmentTransaction).commit();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyTest.java
new file mode 100644
index 0000000..b57c7eb
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.fuelgauge.anomaly;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyTest {
+    private static int TYPE = Anomaly.AnomalyType.WAKE_LOCK;
+    private static int UID = 111;
+    private static long WAKE_LOCK_TIME_MS = 1500;
+    private static String PACKAGE_NAME = "com.android.settings";
+    private static String DISPLAY_NAME = "settings";
+
+    @Test
+    public void testBuilder_buildCorrectly() {
+        Anomaly anomaly = new Anomaly.Builder()
+                .setType(TYPE)
+                .setUid(UID)
+                .setWakeLockTimeMs(WAKE_LOCK_TIME_MS)
+                .setPackageName(PACKAGE_NAME)
+                .setDisplayName(DISPLAY_NAME)
+                .build();
+
+        assertThat(anomaly.type).isEqualTo(TYPE);
+        assertThat(anomaly.uid).isEqualTo(UID);
+        assertThat(anomaly.wakelockTimeMs).isEqualTo(WAKE_LOCK_TIME_MS);
+        assertThat(anomaly.packageName).isEqualTo(PACKAGE_NAME);
+        assertThat(anomaly.displayName).isEqualTo(DISPLAY_NAME);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java
new file mode 100644
index 0000000..8d6d31b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.fuelgauge.anomaly;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.fuelgauge.anomaly.action.ForceStopAction;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyUtilsTest {
+
+    @Test
+    public void testGetAnomalyAction_typeWakeLock_returnForceStop() {
+        assertThat(AnomalyUtils.getAnomalyAction(Anomaly.AnomalyType.WAKE_LOCK)).isInstanceOf(
+                ForceStopAction.class);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/CameraLiftTriggerSuggestionActivityTest.java b/tests/unit/src/com/android/settings/CameraLiftTriggerSuggestionActivityTest.java
new file mode 100644
index 0000000..cbf1db8
--- /dev/null
+++ b/tests/unit/src/com/android/settings/CameraLiftTriggerSuggestionActivityTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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 android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.support.test.espresso.matcher.ViewMatchers.hasSibling;
+import static org.hamcrest.Matchers.allOf;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CameraLiftTriggerSuggestionActivityTest {
+    private Instrumentation mInstrumentation;
+    private Context mTargetContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mTargetContext = mInstrumentation.getTargetContext();
+    }
+
+    @Test
+    public void launchCameraLiftTriggerSuggestion_shouldNotCrash() {
+        final Intent cameraTriggerSuggestionIntent = new Intent(mTargetContext,
+                Settings.CameraLiftTriggerSuggestionActivity.class);
+        final boolean cameraLiftTriggerEnabled = mTargetContext.getResources()
+                .getBoolean(R.bool.config_cameraLiftTriggerAvailable);
+
+        if (!cameraLiftTriggerEnabled) {
+            return;
+        }
+
+        mInstrumentation.startActivitySync(cameraTriggerSuggestionIntent);
+
+        onView(allOf(withText(R.string.camera_lift_trigger_title),
+                          hasSibling(withText(R.string.camera_lift_trigger_summary))))
+                .check(matches(isDisplayed()));
+    }
+}
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) {