Merge "Rename language+country resources to just language"
diff --git a/res/layout/storage_summary_donut.xml b/res/layout/storage_summary_donut.xml
index 9cffe69..d9ead9c 100644
--- a/res/layout/storage_summary_donut.xml
+++ b/res/layout/storage_summary_donut.xml
@@ -14,47 +14,61 @@
      limitations under the License.
 -->
 
-<!-- TODO: Update this view to not match the existing storage summary.-->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
-              android:orientation="vertical"
-              android:minHeight="?android:attr/listPreferredItemHeightSmall"
+              android:orientation="horizontal"
               android:gravity="center_vertical"
-              android:paddingStart="@dimen/preference_no_icon_padding_start"
-              android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-              android:paddingTop="16dip"
-              android:paddingBottom="16dip"
               android:background="?android:attr/selectableItemBackground">
 
-    <TextView
-        android:id="@android:id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        android:textAlignment="viewStart"
-        android:textAppearance="@android:style/TextAppearance.Material.Subhead"
-        android:textColor="?android:attr/colorAccent"
-        android:textSize="36sp"
-        android:ellipsize="marquee"
-        android:fadingEdge="horizontal" />
+    <LinearLayout android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:orientation="vertical"
+                  android:minHeight="?android:attr/listPreferredItemHeightSmall"
+                  android:gravity="center_vertical"
+                  android:paddingStart="@dimen/preference_no_icon_padding_start"
+                  android:paddingEnd="@dimen/storage_summary_padding_end"
+                  android:paddingTop="16dip"
+                  android:paddingBottom="16dip"
+                  android:enabled="false">
 
-    <TextView
-        android:id="@android:id/summary"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textAlignment="viewStart"
-        android:textAppearance="@android:style/TextAppearance.Material.Body1"
-        android:maxLines="10" />
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAlignment="viewStart"
+            android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+            android:textColor="?android:attr/colorAccent"
+            android:textSize="36sp"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
 
-    <ProgressBar
-        android:id="@android:id/progress"
-        android:layout_width="match_parent"
-        android:layout_height="8dp"
-        android:layout_marginTop="16dp"
-        android:layout_marginBottom="8dp"
-        android:visibility="gone"
-        android:max="100"
-        style="?android:attr/progressBarStyleHorizontal" />
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_marginStart="4dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
+            android:textAppearance="@android:style/TextAppearance.Material.Body1"
+            android:maxLines="10" />
+
+        <Button
+            android:id="@+id/deletion_helper_button"
+            android:theme="@style/FreeUpStorageButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/summary"
+            android:text="@string/storage_menu_free"/>
+    </LinearLayout>
+
+    <com.android.settings.widget.DonutView
+        android:id="@+id/donut"
+        android:layout_width="100dp"
+        android:layout_height="100dp"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:minWidth="58dip"
+        android:gravity="end|center_vertical"/>
 
 </LinearLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index a8d35dc..38eb74b 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -111,6 +111,9 @@
     <!-- Gestures settings -->
     <color name="gestures_setting_background_color">#f5f5f5</color>
 
-    <!-- TODO: revert it after the SettingsShadowResources is globally finalized -->
     <color name="status_bar_color">#3c3c3c</color>
+
+    <!-- Color for the background of the donut graph.-->
+    <color name="donut_background_grey">#ffd7d7d7</color>
+
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4b3dba9..bc159d6 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -307,4 +307,7 @@
     <!-- Padding for the escalation card in normal dimens -->
     <dimen name="support_escalation_card_padding_start">40dp</dimen>
     <dimen name="support_escalation_card_padding_end">40dp</dimen>
+
+    <!-- Padding between the donut and the storage summary. -->
+    <dimen name="storage_summary_padding_end">16dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b9e9c1c..73c7e3b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8019,4 +8019,14 @@
     <!-- Preference label for the Files storage section. [CHAR LIMIT=50] -->
     <string name="storage_files">Files</string>
 
+    <!-- Main settings screen item's title to go into the storage settings screen [CHAR LIMIT=25] -->
+    <string name="storage_settings_2" >Phone Storage</string>
+
+    <!-- Summary of a single storage volume used space. [CHAR LIMIT=24] -->
+    <string name="storage_size_large_alternate"><xliff:g id="number" example="128">^1</xliff:g><small><small> <xliff:g id="unit" example="KB">^2</xliff:g> used</small></small></string>
+    <!-- Summary of a single storage volume free space. [CHAR LIMIT=48]-->
+    <string name="storage_volume_free"><xliff:g id="total" example="32GB">%1$s</xliff:g> free</string>
+    <!-- The percent of storage used by a storage volume. Exposed inside of a donut graph. [CHAR LIMIT=4]-->
+    <string name="storage_percent_used"><xliff:g id="percent" example="50%">%1$s</xliff:g>%%</string>
+
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a3baa21..a4a679b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -437,4 +437,10 @@
     </style>
 
     <style name="AppActionPrimaryButton" parent="android:Widget.Material.Button.Colored"/>
+
+    <style name="FreeUpStorageButton">
+        <item name="android:buttonStyle">@android:style/Widget.Material.Button</item>
+        <item name="android:colorButtonNormal">#fff</item>
+    </style>
+
 </resources>
diff --git a/res/xml/storage_dashboard_fragment.xml b/res/xml/storage_dashboard_fragment.xml
index d577267..50287c3 100644
--- a/res/xml/storage_dashboard_fragment.xml
+++ b/res/xml/storage_dashboard_fragment.xml
@@ -21,32 +21,26 @@
         android:key="pref_summary" />
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_photos_videos"
-        android:title="@string/storage_photos_videos"
-        android:fragment="com.android.settings.deletionhelper.AutomaticStorageManagerSettings">
+        android:title="@string/storage_photos_videos">
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_music_audio"
-        android:title="@string/storage_music_audio"
-        android:fragment="com.android.settings.deletionhelper.AutomaticStorageManagerSettings">
+        android:title="@string/storage_music_audio">
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_games"
-        android:title="@string/storage_games"
-        android:fragment="com.android.settings.deletionhelper.AutomaticStorageManagerSettings">
+        android:title="@string/storage_games">
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_other_apps"
-        android:title="@string/storage_other_apps"
-        android:fragment="com.android.settings.deletionhelper.AutomaticStorageManagerSettings">
+        android:title="@string/storage_other_apps">
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_system"
-        android:title="@string/storage_detail_system"
-        android:fragment="com.android.settings.deletionhelper.AutomaticStorageManagerSettings">
+        android:title="@string/storage_detail_system">
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_files"
-        android:title="@string/storage_files"
-        android:fragment="com.android.settings.deletionhelper.AutomaticStorageManagerSettings">
+        android:title="@string/storage_files">
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
 </PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index 418c1e2..49eb1b0 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -492,6 +492,7 @@
         mBluetoothSelectA2dpBitsPerSample = addListPreference(BLUETOOTH_SELECT_A2DP_BITS_PER_SAMPLE_KEY);
         mBluetoothSelectA2dpChannelMode = addListPreference(BLUETOOTH_SELECT_A2DP_CHANNEL_MODE_KEY);
         mBluetoothSelectA2dpLdacPlaybackQuality = addListPreference(BLUETOOTH_SELECT_A2DP_LDAC_PLAYBACK_QUALITY_KEY);
+        initBluetoothConfigurationValues();
 
         mWindowAnimationScale = addListPreference(WINDOW_ANIMATION_SCALE_KEY);
         mTransitionAnimationScale = addListPreference(TRANSITION_ANIMATION_SCALE_KEY);
@@ -699,7 +700,6 @@
             updateUsbConfigurationValues();
         }
 
-        initBluetoothConfigurationValues();
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         if (adapter != null) {
             adapter.getProfileProxy(getActivity(),
diff --git a/src/com/android/settings/accounts/AccountPreferenceController.java b/src/com/android/settings/accounts/AccountPreferenceController.java
index 0ddd366..02610b7 100644
--- a/src/com/android/settings/accounts/AccountPreferenceController.java
+++ b/src/com/android/settings/accounts/AccountPreferenceController.java
@@ -286,6 +286,9 @@
     }
 
     private void updateProfileUi(final UserInfo userInfo) {
+        if (mParent.getPreferenceManager() == null) {
+            return;
+        }
         final Context context = mContext;
         final ProfileData profileData = new ProfileData();
         profileData.userInfo = userInfo;
@@ -402,6 +405,10 @@
     }
 
     private void updateAccountTypes(ProfileData profileData) {
+        if (mParent.getPreferenceManager() == null) {
+            // This could happen if activity is finishing
+            return;
+        }
         profileData.preferenceGroup.removeAll();
         if (profileData.userInfo.isEnabled()) {
             final ArrayList<AccountTypePreference> preferences = getAccountTypePreferences(
diff --git a/src/com/android/settings/dashboard/ExpandPreference.java b/src/com/android/settings/dashboard/ExpandPreference.java
index cfa1836..12ca5ac 100644
--- a/src/com/android/settings/dashboard/ExpandPreference.java
+++ b/src/com/android/settings/dashboard/ExpandPreference.java
@@ -48,7 +48,7 @@
     private void init() {
         setLayoutResource(R.layout.expand_preference);
         setIcon(R.drawable.ic_arrow_down_24dp);
-        setTitle(R.string.wifi_more);
+        setTitle(R.string.advanced_section_header);
         setOrder(999);
     }
 }
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index 5d1ac4a..c9572b3 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -35,6 +35,7 @@
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
 import com.android.settings.widget.FooterPreference;
+import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
 import com.android.settingslib.drawer.CategoryKey;
 
 import java.util.ArrayList;
@@ -45,9 +46,9 @@
     private static final String TAG = "StorageDashboardFrag";
 
     private VolumeInfo mVolume;
-    private long mTotalSize;
 
     private StorageSummaryDonutPreferenceController mSummaryController;
+    private StorageItemPreferenceController mPreferenceController;
 
     private boolean isVolumeValid() {
         return (mVolume != null) && (mVolume.getType() == VolumeInfo.TYPE_PRIVATE)
@@ -68,16 +69,19 @@
         }
 
         final long sharedDataSize = mVolume.getPath().getTotalSpace();
-        mTotalSize = sm.getPrimaryStorageSize();
-        long systemSize = mTotalSize - sharedDataSize;
+        long totalSize = sm.getPrimaryStorageSize();
+        long systemSize = totalSize - sharedDataSize;
 
-        if (mTotalSize <= 0) {
-            mTotalSize = sharedDataSize;
+        if (totalSize <= 0) {
+            totalSize = sharedDataSize;
             systemSize = 0;
         }
 
-        final long usedBytes = mTotalSize - mVolume.getPath().getFreeSpace();
-        mSummaryController.updateBytes(usedBytes, mTotalSize);
+        final long usedBytes = totalSize - mVolume.getPath().getFreeSpace();
+        mSummaryController.updateBytes(usedBytes, totalSize);
+        mPreferenceController.setVolume(mVolume);
+        mPreferenceController.setSystemSize(systemSize);
+        mPreferenceController.startMeasurement();
 
         // Initialize the footer preference to go to the smart storage management.
         final FooterPreference pref = mFooterPreferenceMixin.createFooterPreference();
@@ -85,8 +89,6 @@
         pref.setFragment("com.android.settings.deletionhelper.AutomaticStorageManagerSettings");
         pref.setIcon(R.drawable.ic_settings_storage);
         pref.setEnabled(true);
-
-
     }
 
     @Override
@@ -114,23 +116,15 @@
         final List<PreferenceController> controllers = new ArrayList<>();
         mSummaryController = new StorageSummaryDonutPreferenceController(context);
         controllers.add(mSummaryController);
+
+        StorageManager sm = context.getSystemService(StorageManager.class);
+        mPreferenceController = new StorageItemPreferenceController(context, getLifecycle(), this,
+                mVolume, new StorageManagerVolumeProvider(sm));
+        controllers.add(mPreferenceController);
         controllers.add(new ManageStoragePreferenceController(context));
-        controllers.add(new StorageItemPreferenceController(context, "pref_photos_videos"));
-        controllers.add(new StorageItemPreferenceController(context, "pref_music_audio"));
-        controllers.add(new StorageItemPreferenceController(context, "pref_games"));
-        controllers.add(new StorageItemPreferenceController(context, "pref_other_apps"));
-        controllers.add(new StorageItemPreferenceController(context, "pref_system"));
-        controllers.add(new StorageItemPreferenceController(context, "pref_files"));
         return controllers;
     }
 
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        View root = super.onCreateView(inflater, container, savedInstanceState);
-        // TODO: Add loader to load the storage sizes for the StorageItemPreferenceControllers.
-        return root;
-    }
     /**
      * For Search.
      */
diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
index 6a61072..dce8a25 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
@@ -16,23 +16,87 @@
 
 package com.android.settings.deviceinfo.storage;
 
+import android.app.Fragment;
+import android.content.ActivityNotFoundException;
 import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.VolumeInfo;
+import android.provider.DocumentsContract;
+import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.Log;
 
+import com.android.settings.R;
+import com.android.settings.Settings;
+import com.android.settings.Utils;
+import com.android.settings.applications.ManageApplications;
 import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.Lifecycle;
+import com.android.settings.core.lifecycle.LifecycleObserver;
+import com.android.settings.core.lifecycle.events.OnDestroy;
+import com.android.settings.deviceinfo.StorageItemPreference;
+import com.android.settingslib.deviceinfo.StorageMeasurement;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+import java.util.HashMap;
+
 
 /**
- * StorageItemPreferenceController handles the updating of a single storage preference line item.
+ * StorageItemPreferenceController handles the storage line items which summarize the storage
+ * categorization breakdown.
  */
-public class StorageItemPreferenceController extends PreferenceController {
-    private static final long NOT_YET_SET = -1;
-    private final String mKey;
-    private long mStorageSize;
+public class StorageItemPreferenceController extends PreferenceController
+        implements StorageMeasurement.MeasurementReceiver, LifecycleObserver, OnDestroy {
+    private static final String TAG = "StorageItemPreference";
 
-    public StorageItemPreferenceController(Context context, String key) {
+    @VisibleForTesting
+    static final String PHOTO_KEY = "pref_photos_videos";
+    @VisibleForTesting
+    static final String AUDIO_KEY = "pref_music_audio";
+    @VisibleForTesting
+    static final String GAME_KEY = "pref_games";
+    @VisibleForTesting
+    static final String OTHER_APPS_KEY = "pref_other_apps";
+    @VisibleForTesting
+    static final String SYSTEM_KEY = "pref_system";
+    @VisibleForTesting
+    static final String FILES_KEY = "pref_files";
+
+    private final Fragment mFragment;
+    private final StorageVolumeProvider mSvp;
+    private VolumeInfo mVolume;
+    private final int mUserId;
+    private StorageMeasurement mMeasure;
+    private long mSystemSize;
+    private long mUsedSize;
+
+    private StorageItemPreferenceAlternate mPhotoPreference;
+    private StorageItemPreferenceAlternate mAudioPreference;
+    private StorageItemPreferenceAlternate mGamePreference;
+    private StorageItemPreferenceAlternate mAppPreference;
+    private StorageItemPreferenceAlternate mFilePreference;
+    private StorageItemPreferenceAlternate mSystemPreference;
+
+    private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
+
+    public StorageItemPreferenceController(Context context, Lifecycle lifecycle,
+            Fragment hostFragment, VolumeInfo volume, StorageVolumeProvider svp) {
         super(context);
-        mKey = key;
-        mStorageSize = NOT_YET_SET;
+        mFragment = hostFragment;
+        mVolume = volume;
+        mSvp = svp;
+
+        UserManager um = mContext.getSystemService(UserManager.class);
+        mUserId = um.getUserHandle();
+
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
     }
 
     @Override
@@ -42,28 +106,188 @@
 
     @Override
     public boolean handlePreferenceTreeClick(Preference preference) {
-        return false;
+        if (preference == null) {
+            return false;
+        }
+
+        // TODO: Currently, this reflects the existing behavior for these toggles.
+        //       After the intermediate views are built, swap them in.
+        Intent intent = null;
+        switch (preference.getKey()) {
+            case PHOTO_KEY:
+                intent = getPhotosIntent();
+                break;
+            case AUDIO_KEY:
+                intent = getAudioIntent();
+                break;
+            case GAME_KEY:
+                // TODO: Once app categorization is added, make this section.
+            case OTHER_APPS_KEY:
+                // Because we are likely constructed with a null volume, this is theoretically
+                // possible.
+                if (mVolume == null) {
+                    break;
+                }
+                intent = getAppsIntent();
+                break;
+            case FILES_KEY:
+                intent = getFilesIntent();
+                break;
+        }
+
+        if (intent != null) {
+            intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
+
+            launchIntent(intent);
+            return true;
+        }
+
+        return super.handlePreferenceTreeClick(preference);
     }
 
     @Override
     public String getPreferenceKey() {
-        return mKey;
-    }
-
-    @Override
-    public void updateState(Preference preference) {
-        if (preference == null || mStorageSize == NOT_YET_SET) {
-            return;
-        }
-
-        StorageItemPreferenceAlternate summary = (StorageItemPreferenceAlternate) preference;
-        summary.setStorageSize(mStorageSize);
+        return null;
     }
 
     /**
-     * Sets the amount of bytes used by this storage item.
+     * Sets the storage volume to use for when handling taps.
      */
-    public void setStorageSize(long size) {
-        mStorageSize = size;
+    public void setVolume(VolumeInfo volume) {
+        mVolume = volume;
+    }
+
+    @Override
+    public void onDetailsChanged(StorageMeasurement.MeasurementDetails details) {
+        final long imagesSize = totalValues(details, mUserId,
+                Environment.DIRECTORY_DCIM,
+                Environment.DIRECTORY_PICTURES,
+                Environment.DIRECTORY_MOVIES);
+        if (mPhotoPreference != null) {
+            mPhotoPreference.setStorageSize(imagesSize);
+        }
+
+        final long audioSize = totalValues(details, mUserId,
+                Environment.DIRECTORY_MUSIC,
+                Environment.DIRECTORY_ALARMS,
+                Environment.DIRECTORY_NOTIFICATIONS,
+                Environment.DIRECTORY_RINGTONES,
+                Environment.DIRECTORY_PODCASTS);
+        if (mAudioPreference != null) {
+            mAudioPreference.setStorageSize(audioSize);
+        }
+
+        if (mGamePreference != null) {
+            mGamePreference.setStorageSize(0);
+        }
+
+        final long appSize = details.appsSize.get(mUserId);
+        if (mAppPreference != null) {
+            mAppPreference.setStorageSize(appSize);
+        }
+
+        if (mSystemPreference != null) {
+            mSystemPreference.setStorageSize(mSystemSize);
+        }
+
+        final long downloadsSize = totalValues(details, mUserId, Environment.DIRECTORY_DOWNLOADS);
+        final long miscSize = details.miscSize.get(mUserId);
+        if (mFilePreference != null) {
+            mFilePreference.setStorageSize(downloadsSize + miscSize);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mMeasure != null) {
+            mMeasure.onDestroy();
+        }
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        mPhotoPreference = (StorageItemPreferenceAlternate) screen.findPreference(PHOTO_KEY);
+        mAudioPreference = (StorageItemPreferenceAlternate) screen.findPreference(AUDIO_KEY);
+        mGamePreference = (StorageItemPreferenceAlternate) screen.findPreference(GAME_KEY);
+        mAppPreference = (StorageItemPreferenceAlternate) screen.findPreference(OTHER_APPS_KEY);
+        mSystemPreference = (StorageItemPreferenceAlternate) screen.findPreference(SYSTEM_KEY);
+        mFilePreference = (StorageItemPreferenceAlternate) screen.findPreference(FILES_KEY);
+    }
+
+    /**
+     * Begins an asynchronous storage measurement task for the preferences.
+     */
+    public void startMeasurement() {
+        //TODO: When the GID-based measurement system is completed, swap in the GID impl.
+        mMeasure = new StorageMeasurement(mContext, mVolume, mSvp.findEmulatedForPrivate(mVolume));
+        mMeasure.setReceiver(this);
+        mMeasure.forceMeasure();
+    }
+
+    /**
+     * Sets the system size for the system size preference.
+     * @param systemSize the size of the system in bytes
+     */
+    public void setSystemSize(long systemSize) {
+        mSystemSize = systemSize;
+    }
+
+    private Intent getPhotosIntent() {
+        Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
+        intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "images_root"));
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        return intent;
+    }
+
+    private Intent getAudioIntent() {
+        Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
+        intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "audio_root"));
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        return intent;
+    }
+
+    private Intent getAppsIntent() {
+        Bundle args = new Bundle();
+        args.putString(ManageApplications.EXTRA_CLASSNAME,
+                Settings.StorageUseActivity.class.getName());
+        args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid());
+        args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription());
+        return Utils.onBuildStartFragmentIntent(mContext,
+                ManageApplications.class.getName(), args, null, R.string.apps_storage, null,
+                false);
+    }
+
+    private Intent getFilesIntent() {
+        return mSvp.findEmulatedForPrivate(mVolume).buildBrowseIntent();
+    }
+
+    private void launchIntent(Intent intent) {
+        try {
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
+
+            if (userId == -1) {
+                mFragment.startActivity(intent);
+            } else {
+                mFragment.getActivity().startActivityAsUser(intent, new UserHandle(userId));
+            }
+        } catch (ActivityNotFoundException e) {
+            Log.w(TAG, "No activity found for " + intent);
+        }
+    }
+
+    private static long totalValues(StorageMeasurement.MeasurementDetails details, int userId,
+            String... keys) {
+        long total = 0;
+        HashMap<String, Long> map = details.mediaSize.get(userId);
+        if (map != null) {
+            for (String key : keys) {
+                if (map.containsKey(key)) {
+                    total += map.get(key);
+                }
+            }
+        } else {
+            Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId);
+        }
+        return total;
     }
 }
diff --git a/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreference.java b/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreference.java
index d6fd354..9e39034 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreference.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreference.java
@@ -17,20 +17,23 @@
 package com.android.settings.deviceinfo.storage;
 
 import android.content.Context;
+import android.content.Intent;
+import android.os.storage.StorageManager;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceViewHolder;
 import android.util.AttributeSet;
 import android.util.MathUtils;
 import android.view.View;
-import android.widget.ProgressBar;
+import android.widget.Button;
 
 import com.android.settings.R;
+import com.android.settings.widget.DonutView;
 
 /**
  * StorageSummaryDonutPreference is a preference which summarizes the used and remaining storage left
  * on a given storage volume. It is visualized with a donut graphing the % used.
  */
-public class StorageSummaryDonutPreference extends Preference {
+public class StorageSummaryDonutPreference extends Preference implements View.OnClickListener {
     private int mPercent = -1;
 
     public StorageSummaryDonutPreference(Context context) {
@@ -55,17 +58,23 @@
 
     @Override
     public void onBindViewHolder(PreferenceViewHolder view) {
-        // TODO: Replace the progress bar with a donut.
-        final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
-        if (mPercent != -1) {
-            progress.setVisibility(View.VISIBLE);
-            progress.setProgress(mPercent);
-            progress.setScaleY(7f);
-        } else {
-            progress.setVisibility(View.GONE);
+        super.onBindViewHolder(view);
+        final DonutView donut = (DonutView) view.findViewById(R.id.donut);
+        if (donut != null) {
+            donut.setPercentage(mPercent);
         }
 
-        super.onBindViewHolder(view);
+        final Button deletionHelperButton = (Button) view.findViewById(R.id.deletion_helper_button);
+        if (deletionHelperButton != null) {
+            deletionHelperButton.setOnClickListener(this);
+        }
     }
 
+    @Override
+    public void onClick(View v) {
+        if (v != null && R.id.deletion_helper_button == v.getId()) {
+            Intent intent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
+            getContext().startActivity(intent);
+        }
+    }
 }
diff --git a/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
index 1d2478a..a962669 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
@@ -1,16 +1,33 @@
+/*
+ * 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.content.Context;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
 import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceGroup;
 import android.support.v7.preference.PreferenceScreen;
 import android.text.TextUtils;
 import android.text.format.Formatter;
-import android.util.Log;
-import android.widget.TextView;
 
-import com.android.settings.core.PreferenceController;
 import com.android.settings.R;
+import com.android.settings.core.PreferenceController;
+import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
 
 /**
  * StorgaeSummaryPreferenceController updates the donut storage summary preference to have the
@@ -37,12 +54,13 @@
         StorageSummaryDonutPreference summary = (StorageSummaryDonutPreference) preference;
         final Formatter.BytesResult result = Formatter.formatBytes(mContext.getResources(),
                 mUsedBytes, 0);
-        summary.setTitle(TextUtils.expandTemplate(mContext.getText(R.string.storage_size_large),
-                result.value, result.units));
-        summary.setSummary(mContext.getString(R.string.storage_volume_used,
-                Formatter.formatFileSize(mContext, mTotalBytes)));
-        summary.setEnabled(true);
+        summary.setTitle(TextUtils.expandTemplate(
+                mContext.getText(R.string.storage_size_large_alternate), result.value,
+                result.units));
+        summary.setSummary(mContext.getString(R.string.storage_volume_free,
+                Formatter.formatFileSize(mContext, mTotalBytes - mUsedBytes)));
         summary.setPercent(mUsedBytes, mTotalBytes);
+        summary.setEnabled(true);
     }
 
     @Override
@@ -51,11 +69,6 @@
     }
 
     @Override
-    public boolean handlePreferenceTreeClick(Preference preference) {
-        return false;
-    }
-
-    @Override
     public String getPreferenceKey() {
         return "pref_summary";
     }
@@ -69,4 +82,20 @@
         mUsedBytes = used;
         mTotalBytes = total;
     }
+
+    /**
+     * Updates the state of the donut preference for the next update using volume to summarize.
+     * @param volume VolumeInfo to use to populate the informayion.
+     */
+    public void updateSizes(StorageVolumeProvider svp, VolumeInfo volume) {
+        final long sharedDataSize = volume.getPath().getTotalSpace();
+        long totalSize = svp.getPrimaryStorageSize();
+
+        if (totalSize <= 0) {
+            totalSize = sharedDataSize;
+        }
+
+        final long usedBytes = totalSize - volume.getPath().getFreeSpace();
+        updateBytes(usedBytes, totalSize);
+    }
 }
diff --git a/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java b/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java
index 0dbab44..b9c0796 100644
--- a/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java
+++ b/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -33,9 +34,14 @@
 import android.support.v7.preference.PreferenceScreen;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
 
 import java.text.Collator;
 import java.util.ArrayList;
@@ -44,7 +50,7 @@
 import java.util.List;
 
 public final class AvailableVirtualKeyboardFragment extends SettingsPreferenceFragment
-        implements InputMethodPreference.OnSavePreferenceListener {
+        implements InputMethodPreference.OnSavePreferenceListener, Indexable {
 
     private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>();
     private InputMethodSettingValuesWrapper mInputMethodSettingValues;
@@ -169,4 +175,51 @@
             pref.updatePreferenceViews();
         }
     }
+
+    private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
+        final int subtypeCount = imi.getSubtypeCount();
+        final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
+        for (int index = 0; index < subtypeCount; index++) {
+            allSubtypes.add(imi.getSubtypeAt(index));
+        }
+        return allSubtypes;
+    }
+
+    static List<SearchIndexableRaw> buildSearchIndexOfInputMethods(final Context context,
+            final List<InputMethodInfo> inputMethods, final String screenTitle) {
+        final List<SearchIndexableRaw> indexes = new ArrayList<>();
+        final InputMethodManager imm = (InputMethodManager) context.getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        for (int i = 0; i < inputMethods.size(); i++) {
+            final InputMethodInfo imi = inputMethods.get(i);
+            final ServiceInfo serviceInfo = imi.getServiceInfo();
+            final SearchIndexableRaw index = new SearchIndexableRaw(context);
+            index.key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
+                    .flattenToString();
+            index.title = imi.loadLabel(context.getPackageManager()).toString();
+            index.summaryOn = index.summaryOff = InputMethodAndSubtypeUtil
+                    .getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(imi), context, imi);
+            index.screenTitle = screenTitle;
+            indexes.add(index);
+        }
+        return indexes;
+    }
+
+    public static Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+        @Override
+        public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+            final InputMethodManager imm = context.getSystemService(InputMethodManager.class);
+            final List<InputMethodInfo> enabledInputMethods = imm.getEnabledInputMethodList();
+            final List<InputMethodInfo> disabledInputMethods = new ArrayList<>();
+            for (final InputMethodInfo imi : imm.getInputMethodList()) {
+                if (!enabledInputMethods.contains(imi)) {
+                    disabledInputMethods.add(imi);
+                }
+            }
+            final String screenTitle = context.getString(
+                    R.string.available_virtual_keyboard_category);
+            return buildSearchIndexOfInputMethods(context, disabledInputMethods, screenTitle);
+        }
+    };
 }
diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
index e122244..587d039 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
@@ -17,19 +17,11 @@
 package com.android.settings.inputmethod;
 
 import android.app.Activity;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ServiceInfo;
 import android.hardware.input.InputDeviceIdentifier;
-import android.hardware.input.InputManager;
-import android.hardware.input.KeyboardLayout;
 import android.speech.tts.TtsEngines;
 import android.support.v7.preference.Preference;
-import android.view.InputDevice;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
@@ -215,75 +207,7 @@
             indexable.keywords = context.getString(R.string.keywords_keyboard_and_ime);
             indexables.add(indexable);
 
-            InputMethodSettingValuesWrapper immValues = InputMethodSettingValuesWrapper
-                    .getInstance(context);
-            immValues.refreshAllInputMethodAndSubtypes();
-
-            InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(
-                    Context.INPUT_METHOD_SERVICE);
-
-            // TODO: Move to VirtualKeyboardFragment and AvailableVirtualKeyboardFragment.
-            // All other IMEs.
-            List<InputMethodInfo> inputMethods = immValues.getInputMethodList();
-            final int inputMethodCount = (inputMethods == null ? 0 : inputMethods.size());
-            for (int i = 0; i < inputMethodCount; ++i) {
-                InputMethodInfo inputMethod = inputMethods.get(i);
-                List<InputMethodSubtype> subtypes = inputMethodManager
-                        .getEnabledInputMethodSubtypeList(inputMethod, true);
-                String summary = InputMethodAndSubtypeUtil.getSubtypeLocaleNameListAsSentence(
-                        subtypes, context, inputMethod);
-
-                ServiceInfo serviceInfo = inputMethod.getServiceInfo();
-                ComponentName componentName = new ComponentName(serviceInfo.packageName,
-                        serviceInfo.name);
-
-                indexable = new SearchIndexableRaw(context);
-                indexable.key = componentName.flattenToString();
-                indexable.title = inputMethod.loadLabel(context.getPackageManager()).toString();
-                indexable.summaryOn = summary;
-                indexable.summaryOff = summary;
-                indexable.screenTitle = screenTitle;
-                indexables.add(indexable);
-            }
-
-            // TODO: Move to PhysicalKeyboardFragment.
-            // Hard keyboards
-            InputManager inputManager = (InputManager) context.getSystemService(
-                    Context.INPUT_SERVICE);
-            boolean hasHardKeyboards = false;
-
-            final int[] devices = InputDevice.getDeviceIds();
-            for (int i = 0; i < devices.length; i++) {
-                InputDevice device = InputDevice.getDevice(devices[i]);
-                if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
-                    continue;
-                }
-
-                hasHardKeyboards = true;
-
-                InputDeviceIdentifier identifier = device.getIdentifier();
-                String keyboardLayoutDescriptor =
-                        inputManager.getCurrentKeyboardLayoutForInputDevice(identifier);
-                KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
-                        inputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
-
-                String summary;
-                if (keyboardLayout != null) {
-                    summary = keyboardLayout.toString();
-                } else {
-                    summary = context.getString(R.string.keyboard_layout_default_label);
-                }
-
-                indexable = new SearchIndexableRaw(context);
-                indexable.key = device.getName();
-                indexable.title = device.getName();
-                indexable.summaryOn = summary;
-                indexable.summaryOff = summary;
-                indexable.screenTitle = screenTitle;
-                indexables.add(indexable);
-            }
-
-            if (hasHardKeyboards) {
+            if (!PhysicalKeyboardFragment.getPhysicalFullKeyboards().isEmpty()) {
                 // Hard keyboard category.
                 indexable = new SearchIndexableRaw(context);
                 indexable.key = "builtin_keyboard_settings";
diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
index 5dd5ca1..1ecb250 100644
--- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
+++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
@@ -49,6 +49,9 @@
 import com.android.settings.R;
 import com.android.settings.Settings;
 import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
 
 import java.text.Collator;
 import java.util.ArrayList;
@@ -59,7 +62,7 @@
 import java.util.Objects;
 
 public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment
-        implements InputManager.InputDeviceListener {
+        implements InputManager.InputDeviceListener, Indexable {
 
     private static final String KEYBOARD_ASSISTANCE_CATEGORY = "keyboard_assistance_category";
     private static final String SHOW_VIRTUAL_KEYBOARD_SWITCH = "show_virtual_keyboard_switch";
@@ -528,4 +531,43 @@
         }
     }
 
+    public static List<InputDevice> getPhysicalFullKeyboards() {
+        List<InputDevice> keyboards = null;
+        for (final int deviceId : InputDevice.getDeviceIds()) {
+            final InputDevice device = InputDevice.getDevice(deviceId);
+            if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
+                if (keyboards == null) keyboards = new ArrayList<>();
+                keyboards.add(device);
+            }
+        }
+        return (keyboards == null) ? Collections.emptyList() : keyboards;
+    }
+
+    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+        @Override
+        public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+            final InputManager inputManager = (InputManager) context.getSystemService(
+                    Context.INPUT_SERVICE);
+            final String screenTitle = context.getString(R.string.physical_keyboard_title);
+            final List<SearchIndexableRaw> indexes = new ArrayList<>();
+            for (final InputDevice device : getPhysicalFullKeyboards()) {
+                final String keyboardLayoutDescriptor = inputManager
+                        .getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
+                final KeyboardLayout keyboardLayout = (keyboardLayoutDescriptor != null)
+                        ? inputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
+                final String summary = (keyboardLayout != null)
+                        ? keyboardLayout.toString()
+                        : context.getString(R.string.keyboard_layout_default_label);
+                final SearchIndexableRaw index = new SearchIndexableRaw(context);
+                index.key = device.getName();
+                index.title = device.getName();
+                index.summaryOn = summary;
+                index.summaryOff = summary;
+                index.screenTitle = screenTitle;
+                indexes.add(index);
+            }
+            return indexes;
+        }
+    };
 }
diff --git a/src/com/android/settings/inputmethod/VirtualKeyboardFragment.java b/src/com/android/settings/inputmethod/VirtualKeyboardFragment.java
index 24c2ebc..da5cb04 100644
--- a/src/com/android/settings/inputmethod/VirtualKeyboardFragment.java
+++ b/src/com/android/settings/inputmethod/VirtualKeyboardFragment.java
@@ -26,10 +26,15 @@
 import android.support.v7.preference.Preference;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.Preconditions;
 import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
 
 import java.text.Collator;
 import java.util.ArrayList;
@@ -37,7 +42,7 @@
 import java.util.Comparator;
 import java.util.List;
 
-public final class VirtualKeyboardFragment extends SettingsPreferenceFragment {
+public final class VirtualKeyboardFragment extends SettingsPreferenceFragment implements Indexable {
 
     private static final String ADD_VIRTUAL_KEYBOARD_SCREEN = "add_virtual_keyboard_screen";
     private static final Drawable NO_ICON = new ColorDrawable(Color.TRANSPARENT);
@@ -117,4 +122,16 @@
         mAddVirtualKeyboardScreen.setOrder(N);
         getPreferenceScreen().addPreference(mAddVirtualKeyboardScreen);
     }
+
+    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+        @Override
+        public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+            final InputMethodManager imm = context.getSystemService(InputMethodManager.class);
+            final List<InputMethodInfo> enabledInputMethods = imm.getEnabledInputMethodList();
+            final String screenTitle = context.getString(R.string.virtual_keyboard_category);
+            return AvailableVirtualKeyboardFragment
+                    .buildSearchIndexOfInputMethods(context, enabledInputMethods, screenTitle);
+        }
+    };
 }
diff --git a/src/com/android/settings/search/DynamicIndexableContentMonitor.java b/src/com/android/settings/search/DynamicIndexableContentMonitor.java
index 34cdeba..048658c 100644
--- a/src/com/android/settings/search/DynamicIndexableContentMonitor.java
+++ b/src/com/android/settings/search/DynamicIndexableContentMonitor.java
@@ -36,6 +36,7 @@
 import android.print.PrintManager;
 import android.print.PrintServicesLoader;
 import android.printservice.PrintServiceInfo;
+import android.provider.Settings;
 import android.provider.UserDictionary;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
@@ -47,7 +48,10 @@
 
 import com.android.internal.content.PackageMonitor;
 import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
+import com.android.settings.inputmethod.PhysicalKeyboardFragment;
+import com.android.settings.inputmethod.VirtualKeyboardFragment;
 import com.android.settings.print.PrintSettingsFragment;
 
 import java.util.ArrayList;
@@ -205,8 +209,7 @@
         }
 
         private void buildIndex(boolean rebuild) {
-            // TODO: Fix landing page to PhysicalKeyboardFragment.
-            mIndex.updateFromClassNameResource(InputMethodAndLanguageSettings.class.getName(),
+            mIndex.updateFromClassNameResource(PhysicalKeyboardFragment.class.getName(),
                     rebuild, true /* includeInSearchResult */);
         }
 
@@ -380,6 +383,9 @@
     // Also it monitors user dictionary changes and updates search index.
     private static class InputMethodServicesMonitor extends ContentObserver {
 
+        private static final Uri ENABLED_INPUT_METHODS_CONTENT_URI =
+                Settings.Secure.getUriFor(Settings.Secure.ENABLED_INPUT_METHODS);
+
         // Null if not initialized.
         @Nullable private Index mIndex;
         private PackageManager mPackageManager;
@@ -418,7 +424,11 @@
             mPackageManager = context.getPackageManager();
             mContentResolver = context.getContentResolver();
             mInputMethodServices.clear();
+            // Build index of {@link UserDictionary}.
             buildIndex(InputMethodAndLanguageSettings.class, true /* rebuild */);
+            // Build index of IMEs.
+            buildIndex(VirtualKeyboardFragment.class, true /* rebuild */);
+            buildIndex(AvailableVirtualKeyboardFragment.class, true /* rebuild */);
 
             // Cache IME service packages to know when they go away.
             final InputMethodManager inputMethodManager = (InputMethodManager) context
@@ -433,8 +443,9 @@
             // Watch for related content URIs.
             mContentResolver.registerContentObserver(UserDictionary.Words.CONTENT_URI,
                     true /* notifyForDescendants */, this /* observer */);
-            // TODO: Should monitor android.provider.Settings.Secure.ENABLED_INPUT_METHODS and
-            // update index of AvailableVirtualKeyboardFragment and VirtualKeyboardFragment.
+            // Watch for changing enabled IMEs.
+            mContentResolver.registerContentObserver(ENABLED_INPUT_METHODS_CONTENT_URI,
+                    false /* notifyForDescendants */, this /* observer */);
         }
 
         private void buildIndex(Class<?> indexClass, boolean rebuild) {
@@ -451,20 +462,23 @@
                     .queryIntentServices(intent, 0 /* flags */);
             if (services == null || services.isEmpty()) return;
             mInputMethodServices.add(packageName);
-            // TODO: Fix landing page to VirtualKeyboardFragment.
-            buildIndex(InputMethodAndLanguageSettings.class, false /* rebuild */);
+            buildIndex(VirtualKeyboardFragment.class, false /* rebuild */);
+            buildIndex(AvailableVirtualKeyboardFragment.class, false /* rebuild */);
         }
 
         synchronized void onPackageUnavailable(String packageName) {
             if (mIndex == null) return;
             if (!mInputMethodServices.remove(packageName)) return;
-            // TODO: Fix landing page to AvailableVirtualKeyboardFragment.
-            buildIndex(InputMethodAndLanguageSettings.class, true /* rebuild */);
+            buildIndex(VirtualKeyboardFragment.class, true /* rebuild */);
+            buildIndex(AvailableVirtualKeyboardFragment.class, true /* rebuild */);
         }
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            if (UserDictionary.Words.CONTENT_URI.equals(uri)) {
+            if (ENABLED_INPUT_METHODS_CONTENT_URI.equals(uri)) {
+                buildIndex(VirtualKeyboardFragment.class, true /* rebuild */);
+                buildIndex(AvailableVirtualKeyboardFragment.class, true /* rebuild */);
+            } else if (UserDictionary.Words.CONTENT_URI.equals(uri)) {
                 buildIndex(InputMethodAndLanguageSettings.class, true /* rebuild */);
             }
         }
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index 4ecbcf1..831244b 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -54,7 +54,10 @@
 import com.android.settings.gestures.GestureSettings;
 import com.android.settings.gestures.PickupGestureSettings;
 import com.android.settings.gestures.SwipeToNotificationSettings;
+import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
+import com.android.settings.inputmethod.PhysicalKeyboardFragment;
+import com.android.settings.inputmethod.VirtualKeyboardFragment;
 import com.android.settings.location.LocationSettings;
 import com.android.settings.location.ScanningSettings;
 import com.android.settings.network.NetworkDashboardFragment;
@@ -141,6 +144,10 @@
                 R.drawable.ic_settings_accounts);
         addIndex(InputMethodAndLanguageSettings.class,
                 NO_DATA_RES_ID, R.drawable.ic_settings_language);
+        addIndex(VirtualKeyboardFragment.class, NO_DATA_RES_ID, R.drawable.ic_settings_language);
+        addIndex(AvailableVirtualKeyboardFragment.class,
+                NO_DATA_RES_ID, R.drawable.ic_settings_language);
+        addIndex(PhysicalKeyboardFragment.class, NO_DATA_RES_ID, R.drawable.ic_settings_language);
         addIndex(PrivacySettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_backup);
         addIndex(DateTimeSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_date_time);
         addIndex(AccessibilitySettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_accessibility);
diff --git a/src/com/android/settings/search2/CursorToSearchResultConverter.java b/src/com/android/settings/search2/CursorToSearchResultConverter.java
index 3255c0c..540932c 100644
--- a/src/com/android/settings/search2/CursorToSearchResultConverter.java
+++ b/src/com/android/settings/search2/CursorToSearchResultConverter.java
@@ -30,7 +30,6 @@
 import android.util.Log;
 import com.android.settings.SettingsActivity;
 import com.android.settings.Utils;
-import com.android.settings.search.Index;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -41,6 +40,8 @@
 import java.util.Set;
 
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_TITLE;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_CLASS_NAME;
@@ -176,7 +177,7 @@
             String className, String pkgName ) {
         IntentPayload payload;
         if (TextUtils.isEmpty(action)) {
-            final String screenTitle = cursor.getString(Index.COLUMN_INDEX_SCREEN_TITLE);
+            final String screenTitle = cursor.getString(COLUMN_INDEX_SCREEN_TITLE);
             // Action is null, we will launch it as a sub-setting
             final Bundle args = new Bundle();
             args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);
@@ -185,8 +186,7 @@
             payload = new IntentPayload(intent);
         } else {
             final Intent intent = new Intent(action);
-            final String targetClass = cursor.getString(
-                    Index.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS);
+            final String targetClass = cursor.getString(COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS);
             if (!TextUtils.isEmpty(pkgName) && !TextUtils.isEmpty(targetClass)) {
                 final ComponentName component = new ComponentName(pkgName, targetClass);
                 intent.setComponent(component);
diff --git a/src/com/android/settings/widget/DonutView.java b/src/com/android/settings/widget/DonutView.java
new file mode 100644
index 0000000..845f52c
--- /dev/null
+++ b/src/com/android/settings/widget/DonutView.java
@@ -0,0 +1,102 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+/**
+ * DonutView represents a donut graph. It visualizes a certain percentage of fullness with a
+ * corresponding label with the fullness on the inside (i.e. "50%" inside of the donut.
+ */
+public class DonutView extends View {
+    private static final int TOP = -90;
+    private float mStrokeWidth;
+    private int mPercent;
+    private Paint mBackgroundCircle;
+    private Paint mFilledArc;
+    private TextPaint mTextPaint;
+
+    public DonutView(Context context) {
+        super(context);
+    }
+
+    public DonutView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        float density = getResources().getDisplayMetrics().density;
+        mStrokeWidth = 10f * density;
+
+        mBackgroundCircle = new Paint();
+        mBackgroundCircle.setAntiAlias(true);
+        mBackgroundCircle.setStrokeCap(Paint.Cap.BUTT);
+        mBackgroundCircle.setStyle(Paint.Style.STROKE);
+        mBackgroundCircle.setStrokeWidth(mStrokeWidth);
+        mBackgroundCircle.setColor(getResources().getColor(R.color.donut_background_grey));
+
+        mFilledArc = new Paint();
+        mFilledArc.setAntiAlias(true);
+        mFilledArc.setStrokeCap(Paint.Cap.BUTT);
+        mFilledArc.setStyle(Paint.Style.STROKE);
+        mFilledArc.setStrokeWidth(mStrokeWidth);
+        mFilledArc.setColor(Utils.getColorAccent(getContext()));
+
+        mTextPaint = new TextPaint();
+        mTextPaint.setColor(Utils.getColorAccent(getContext()));
+        mTextPaint.setAntiAlias(true);
+        mTextPaint.setTextSize(18f * density);
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        canvas.drawArc(0 + mStrokeWidth, 0 + mStrokeWidth, getWidth() - mStrokeWidth,
+                getHeight() - mStrokeWidth, TOP, 360, false, mBackgroundCircle);
+
+        canvas.drawArc(0 + mStrokeWidth, 0 + mStrokeWidth, getWidth() - mStrokeWidth,
+                getHeight() - mStrokeWidth, TOP, (360 * mPercent / 100), false, mFilledArc);
+
+        int centerX = getWidth() / 2;
+        int centerY = getHeight() / 2;
+
+        String percentString =
+                String.format(getContext().getString(R.string.storage_percent_used), mPercent);
+        // drawText uses the Y dimension as the floor of the text, so we do this to center.
+        canvas.drawText(percentString, centerX,
+                centerY + getTextHeight(mTextPaint) / 2 - mTextPaint.descent(),
+                mTextPaint);
+    }
+
+    /**
+     * Set a percentage full to have the donut graph.
+     */
+    public void setPercentage(int percent) {
+        mPercent = percent;
+        invalidate();
+    }
+
+    private float getTextHeight(TextPaint paint) {
+        // Technically, this should be the cap height, but I can live with the descent - ascent.
+        return paint.descent() - paint.ascent();
+    }
+}
diff --git a/src/com/android/settings/wifi/AllowRecommendationPreferenceController.java b/src/com/android/settings/wifi/AllowRecommendationPreferenceController.java
new file mode 100644
index 0000000..a58055d
--- /dev/null
+++ b/src/com/android/settings/wifi/AllowRecommendationPreferenceController.java
@@ -0,0 +1,65 @@
+/*
+ * 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.wifi;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+
+import com.android.settings.core.PreferenceController;
+
+public class AllowRecommendationPreferenceController extends PreferenceController {
+
+    private static final String KEY_ALLOW_RECOMMENDATIONS = "allow_recommendations";
+
+    public AllowRecommendationPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        ((SwitchPreference) preference).setChecked(Settings.Global.getInt(
+                mContext.getContentResolver(),
+                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1);
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (!TextUtils.equals(preference.getKey(), KEY_ALLOW_RECOMMENDATIONS)) {
+            return false;
+        }
+        if (!(preference instanceof SwitchPreference)) {
+            return false;
+        }
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+                ((SwitchPreference) preference).isChecked() ? 1 : 0);
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_ALLOW_RECOMMENDATIONS;
+    }
+}
diff --git a/src/com/android/settings/wifi/CellularFallbackPreferenceController.java b/src/com/android/settings/wifi/CellularFallbackPreferenceController.java
new file mode 100644
index 0000000..4e9174c
--- /dev/null
+++ b/src/com/android/settings/wifi/CellularFallbackPreferenceController.java
@@ -0,0 +1,85 @@
+/*
+ * 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.wifi;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+
+import com.android.settings.core.PreferenceController;
+
+/**
+ * {@link PreferenceController} that controls whether we should fall back to celluar when wifi is
+ * bad.
+ */
+public class CellularFallbackPreferenceController extends PreferenceController {
+
+    private static final String KEY_CELLULAR_FALLBACK = "wifi_cellular_data_fallback";
+
+
+    public CellularFallbackPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return !avoidBadWifiConfig();
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_CELLULAR_FALLBACK;
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (!TextUtils.equals(preference.getKey(), KEY_CELLULAR_FALLBACK)) {
+            return false;
+        }
+        if (!(preference instanceof SwitchPreference)) {
+            return false;
+        }
+        // On: avoid bad wifi. Off: prompt.
+        String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI;
+        Settings.Global.putString(mContext.getContentResolver(), settingName,
+                ((SwitchPreference) preference).isChecked() ? "1" : null);
+        return true;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        final boolean currentSetting = avoidBadWifiCurrentSettings();
+        // TODO: can this ever be null? The return value of avoidBadWifiConfig() can only
+        // change if the resources change, but if that happens the activity will be recreated...
+        if (preference != null) {
+            SwitchPreference pref = (SwitchPreference) preference;
+            pref.setChecked(currentSetting);
+        }
+    }
+
+    private boolean avoidBadWifiConfig() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_networkAvoidBadWifi) == 1;
+    }
+
+    private boolean avoidBadWifiCurrentSettings() {
+        return "1".equals(Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.NETWORK_AVOID_BAD_WIFI));
+    }
+}
diff --git a/src/com/android/settings/wifi/ConfigureWifiSettings.java b/src/com/android/settings/wifi/ConfigureWifiSettings.java
index 01ae99c..98f68ee 100644
--- a/src/com/android/settings/wifi/ConfigureWifiSettings.java
+++ b/src/com/android/settings/wifi/ConfigureWifiSettings.java
@@ -15,222 +15,56 @@
  */
 package com.android.settings.wifi;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.support.v14.preference.SwitchPreference;
-import android.support.v7.preference.ListPreference;
-import android.support.v7.preference.Preference;
-import android.text.TextUtils;
-import android.util.Log;
-import android.widget.Toast;
+
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.Utils;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.dashboard.DashboardFragment;
+
+import java.util.ArrayList;
 import java.util.List;
 
-public class ConfigureWifiSettings extends SettingsPreferenceFragment
-        implements Preference.OnPreferenceChangeListener {
+import static android.content.Context.WIFI_SERVICE;
+
+public class ConfigureWifiSettings extends DashboardFragment {
+
     private static final String TAG = "ConfigureWifiSettings";
 
-    private static final String KEY_MAC_ADDRESS = "mac_address";
-    private static final String KEY_SAVED_NETWORKS = "saved_networks";
-    private static final String KEY_CURRENT_IP_ADDRESS = "current_ip_address";
-    private static final String KEY_NOTIFY_OPEN_NETWORKS = "notify_open_networks";
-    private static final String KEY_SLEEP_POLICY = "sleep_policy";
-    private static final String KEY_CELLULAR_FALLBACK = "wifi_cellular_data_fallback";
-    private static final String KEY_ALLOW_RECOMMENDATIONS = "allow_recommendations";
-
     private WifiManager mWifiManager;
-    private IntentFilter mFilter;
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        addPreferencesFromResource(R.xml.wifi_configure_settings);
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
-        mFilter = new IntentFilter();
-        mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
-        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        initPreferences();
-        getActivity().registerReceiver(mReceiver, mFilter);
-        refreshWifiInfo();
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        getActivity().unregisterReceiver(mReceiver);
-    }
-
-    private void initPreferences() {
-        List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
-        if (configs == null || configs.size() == 0) {
-            removePreference(KEY_SAVED_NETWORKS);
-        }
-
-        SwitchPreference notifyOpenNetworks =
-                (SwitchPreference) findPreference(KEY_NOTIFY_OPEN_NETWORKS);
-        notifyOpenNetworks.setChecked(Settings.Global.getInt(getContentResolver(),
-                Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);
-        notifyOpenNetworks.setEnabled(mWifiManager.isWifiEnabled());
-
-        final Context context = getActivity();
-        if (avoidBadWifiConfig()) {
-            // Hide preference toggle, always avoid bad wifi networks.
-            removePreference(KEY_CELLULAR_FALLBACK);
-        } else {
-            // Show preference toggle, initialized based on current settings value.
-            boolean currentSetting = avoidBadWifiCurrentSettings();
-            SwitchPreference pref = (SwitchPreference) findPreference(KEY_CELLULAR_FALLBACK);
-            // TODO: can this ever be null? The return value of avoidBadWifiConfig() can only
-            // change if the resources change, but if that happens the activity will be recreated...
-            if (pref != null) {
-                pref.setChecked(currentSetting);
-            }
-        }
-
-        SwitchPreference allowRecommendations =
-            (SwitchPreference) findPreference(KEY_ALLOW_RECOMMENDATIONS);
-        allowRecommendations.setChecked(Settings.Global.getInt(getContentResolver(),
-            Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1);
-
-        ListPreference sleepPolicyPref = (ListPreference) findPreference(KEY_SLEEP_POLICY);
-        if (sleepPolicyPref != null) {
-            if (Utils.isWifiOnly(context)) {
-                sleepPolicyPref.setEntries(R.array.wifi_sleep_policy_entries_wifi_only);
-            }
-            sleepPolicyPref.setOnPreferenceChangeListener(this);
-            int value = Settings.Global.getInt(getContentResolver(),
-                    Settings.Global.WIFI_SLEEP_POLICY,
-                    Settings.Global.WIFI_SLEEP_POLICY_NEVER);
-            String stringValue = String.valueOf(value);
-            sleepPolicyPref.setValue(stringValue);
-            updateSleepPolicySummary(sleepPolicyPref, stringValue);
-        }
-    }
-
-    private void updateSleepPolicySummary(Preference sleepPolicyPref, String value) {
-        if (value != null) {
-            String[] values = getResources().getStringArray(R.array.wifi_sleep_policy_values);
-            final int summaryArrayResId = Utils.isWifiOnly(getActivity()) ?
-                    R.array.wifi_sleep_policy_entries_wifi_only : R.array.wifi_sleep_policy_entries;
-            String[] summaries = getResources().getStringArray(summaryArrayResId);
-            for (int i = 0; i < values.length; i++) {
-                if (value.equals(values[i])) {
-                    if (i < summaries.length) {
-                        sleepPolicyPref.setSummary(summaries[i]);
-                        return;
-                    }
-                }
-            }
-        }
-
-        sleepPolicyPref.setSummary("");
-        Log.e(TAG, "Invalid sleep policy value: " + value);
-    }
-
-    private boolean avoidBadWifiConfig() {
-        return getActivity().getResources().getInteger(
-                com.android.internal.R.integer.config_networkAvoidBadWifi) == 1;
-    }
-
-    private boolean avoidBadWifiCurrentSettings() {
-        return "1".equals(Settings.Global.getString(getContentResolver(),
-                Settings.Global.NETWORK_AVOID_BAD_WIFI));
-    }
-
-    @Override
-    public boolean onPreferenceTreeClick(Preference preference) {
-        String key = preference.getKey();
-
-        if (KEY_NOTIFY_OPEN_NETWORKS.equals(key)) {
-            Settings.Global.putInt(getContentResolver(),
-                    Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
-                    ((SwitchPreference) preference).isChecked() ? 1 : 0);
-        } else if (KEY_CELLULAR_FALLBACK.equals(key)) {
-            // On: avoid bad wifi. Off: prompt.
-            String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI;
-            Settings.Global.putString(getContentResolver(), settingName,
-                    ((SwitchPreference) preference).isChecked() ? "1" : null);
-        } else if (KEY_ALLOW_RECOMMENDATIONS.equals(key)) {
-            Settings.Global.putInt(getActivity().getContentResolver(),
-                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
-                ((SwitchPreference) preference).isChecked() ? 1 : 0);
-        } else {
-            return super.onPreferenceTreeClick(preference);
-        }
-        return true;
-    }
-
-    @Override
-    public boolean onPreferenceChange(Preference preference, Object newValue) {
-        final Context context = getActivity();
-        String key = preference.getKey();
-
-        if (KEY_SLEEP_POLICY.equals(key)) {
-            try {
-                String stringValue = (String) newValue;
-                Settings.Global.putInt(getContentResolver(), Settings.Global.WIFI_SLEEP_POLICY,
-                        Integer.parseInt(stringValue));
-                updateSleepPolicySummary(preference, stringValue);
-            } catch (NumberFormatException e) {
-                Toast.makeText(context, R.string.wifi_setting_sleep_policy_error,
-                        Toast.LENGTH_SHORT).show();
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    private void refreshWifiInfo() {
-        final Context context = getActivity();
-        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
-
-        Preference wifiMacAddressPref = findPreference(KEY_MAC_ADDRESS);
-        String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
-        wifiMacAddressPref.setSummary(!TextUtils.isEmpty(macAddress) ? macAddress
-                : context.getString(R.string.status_unavailable));
-        wifiMacAddressPref.setSelectable(false);
-
-        Preference wifiIpAddressPref = findPreference(KEY_CURRENT_IP_ADDRESS);
-        String ipAddress = Utils.getWifiIpAddresses(context);
-        wifiIpAddressPref.setSummary(ipAddress == null ?
-                context.getString(R.string.status_unavailable) : ipAddress);
-        wifiIpAddressPref.setSelectable(false);
-    }
 
     @Override
     public int getMetricsCategory() {
         return MetricsEvent.CONFIGURE_WIFI;
     }
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION) ||
-                action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
-                refreshWifiInfo();
-            }
-        }
-    };
+    @Override
+    protected String getCategoryKey() {
+        // We don't want to inject any external settings into this screen.
+        return null;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.wifi_configure_settings;
+    }
+
+    @Override
+    protected List<PreferenceController> getPreferenceControllers(Context context) {
+        mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
+        final List<PreferenceController> controllers = new ArrayList<>();
+        controllers.add(new WifiInfoPreferenceController(context, getLifecycle(), mWifiManager));
+        controllers.add(new SavedNetworkPreferenceController(context, mWifiManager));
+        controllers.add(new NotifyOpenNetworksPreferenceController(context, mWifiManager));
+        controllers.add(new CellularFallbackPreferenceController(context));
+        controllers.add(new AllowRecommendationPreferenceController(context));
+        controllers.add(new WifiSleepPolicyPreferenceController(context));
+        return controllers;
+    }
 }
diff --git a/src/com/android/settings/wifi/NotifyOpenNetworksPreferenceController.java b/src/com/android/settings/wifi/NotifyOpenNetworksPreferenceController.java
new file mode 100644
index 0000000..df3aa26
--- /dev/null
+++ b/src/com/android/settings/wifi/NotifyOpenNetworksPreferenceController.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.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+
+import com.android.settings.core.PreferenceController;
+
+/**
+ * {@link PreferenceController} that controls whether we should notify user when open network is
+ * available.
+ */
+public class NotifyOpenNetworksPreferenceController extends PreferenceController {
+
+    private static final String KEY_NOTIFY_OPEN_NETWORKS = "notify_open_networks";
+    private final WifiManager mWifiManager;
+
+    public NotifyOpenNetworksPreferenceController(Context context, WifiManager wifiManager) {
+        super(context);
+        mWifiManager = wifiManager;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (!TextUtils.equals(preference.getKey(), KEY_NOTIFY_OPEN_NETWORKS)) {
+            return false;
+        }
+        if (!(preference instanceof SwitchPreference)) {
+            return false;
+        }
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+                ((SwitchPreference) preference).isChecked() ? 1 : 0);
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_NOTIFY_OPEN_NETWORKS;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        if (!(preference instanceof SwitchPreference)) {
+            return;
+        }
+        final SwitchPreference notifyOpenNetworks = (SwitchPreference) preference;
+        notifyOpenNetworks.setChecked(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);
+        notifyOpenNetworks.setEnabled(mWifiManager.isWifiEnabled());
+    }
+}
diff --git a/src/com/android/settings/wifi/SavedNetworkPreferenceController.java b/src/com/android/settings/wifi/SavedNetworkPreferenceController.java
new file mode 100644
index 0000000..c3ad355
--- /dev/null
+++ b/src/com/android/settings/wifi/SavedNetworkPreferenceController.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.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+
+import com.android.settings.core.PreferenceController;
+
+import java.util.List;
+
+/**
+ * {@link PreferenceController} that opens saved network subsetting.
+ */
+public class SavedNetworkPreferenceController extends PreferenceController {
+
+    private static final String KEY_SAVED_NETWORKS = "saved_networks";
+
+    private final WifiManager mWifiManager;
+
+    public SavedNetworkPreferenceController(Context context, WifiManager wifiManager) {
+        super(context);
+        mWifiManager = wifiManager;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        final List<WifiConfiguration> config = mWifiManager.getConfiguredNetworks();
+        return config != null && !config.isEmpty();
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_SAVED_NETWORKS;
+    }
+}
diff --git a/src/com/android/settings/wifi/WifiInfoPreferenceController.java b/src/com/android/settings/wifi/WifiInfoPreferenceController.java
new file mode 100644
index 0000000..f88e38b
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiInfoPreferenceController.java
@@ -0,0 +1,120 @@
+/*
+ * 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.wifi;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.Lifecycle;
+import com.android.settings.core.lifecycle.LifecycleObserver;
+import com.android.settings.core.lifecycle.events.OnPause;
+import com.android.settings.core.lifecycle.events.OnResume;
+
+/**
+ * {@link PreferenceController} that updates MAC/IP address.
+ */
+public class WifiInfoPreferenceController extends PreferenceController implements
+        LifecycleObserver, OnResume, OnPause {
+
+    private static final String KEY_CURRENT_IP_ADDRESS = "current_ip_address";
+    private static final String KEY_MAC_ADDRESS = "mac_address";
+
+    private final IntentFilter mFilter;
+    private final WifiManager mWifiManager;
+
+    private Preference mWifiMacAddressPref;
+    private Preference mWifiIpAddressPref;
+
+    public WifiInfoPreferenceController(Context context, Lifecycle lifecycle,
+            WifiManager wifiManager) {
+        super(context);
+        mWifiManager = wifiManager;
+        mFilter = new IntentFilter();
+        mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
+        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+
+        lifecycle.addObserver(this);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        // Returns null because this controller contains more than 1 preference.
+        return null;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mWifiMacAddressPref = screen.findPreference(KEY_MAC_ADDRESS);
+        mWifiMacAddressPref.setSelectable(false);
+        mWifiIpAddressPref = screen.findPreference(KEY_CURRENT_IP_ADDRESS);
+        mWifiIpAddressPref.setSelectable(false);
+    }
+
+    @Override
+    public void onResume() {
+        mContext.registerReceiver(mReceiver, mFilter);
+        updateWifiInfo();
+    }
+
+    @Override
+    public void onPause() {
+        mContext.unregisterReceiver(mReceiver);
+    }
+
+    public void updateWifiInfo() {
+        if (mWifiMacAddressPref != null) {
+            final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+            final String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
+            mWifiMacAddressPref.setSummary(!TextUtils.isEmpty(macAddress)
+                    ? macAddress
+                    : mContext.getString(R.string.status_unavailable));
+        }
+        if (mWifiIpAddressPref != null) {
+            final String ipAddress = Utils.getWifiIpAddresses(mContext);
+            mWifiIpAddressPref.setSummary(ipAddress == null
+                    ? mContext.getString(R.string.status_unavailable)
+                    : ipAddress);
+        }
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION) ||
+                    action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                updateWifiInfo();
+            }
+        }
+    };
+}
diff --git a/src/com/android/settings/wifi/WifiSleepPolicyPreferenceController.java b/src/com/android/settings/wifi/WifiSleepPolicyPreferenceController.java
new file mode 100644
index 0000000..3e7911b
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiSleepPolicyPreferenceController.java
@@ -0,0 +1,104 @@
+/*
+ * 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.wifi;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.Preference;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.PreferenceController;
+
+import static com.android.internal.os.MemoryPowerCalculator.TAG;
+
+public class WifiSleepPolicyPreferenceController extends PreferenceController implements
+        Preference.OnPreferenceChangeListener {
+
+    private static final String KEY_SLEEP_POLICY = "sleep_policy";
+
+    public WifiSleepPolicyPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_SLEEP_POLICY;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        ListPreference sleepPolicyPref = (ListPreference) preference;
+        if (sleepPolicyPref != null) {
+            if (Utils.isWifiOnly(mContext)) {
+                sleepPolicyPref.setEntries(R.array.wifi_sleep_policy_entries_wifi_only);
+            }
+            int value = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.WIFI_SLEEP_POLICY,
+                    Settings.Global.WIFI_SLEEP_POLICY_NEVER);
+            String stringValue = String.valueOf(value);
+            sleepPolicyPref.setValue(stringValue);
+            updateSleepPolicySummary(sleepPolicyPref, stringValue);
+        }
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        try {
+            String stringValue = (String) newValue;
+            Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_SLEEP_POLICY,
+                    Integer.parseInt(stringValue));
+            updateSleepPolicySummary(preference, stringValue);
+        } catch (NumberFormatException e) {
+            Toast.makeText(mContext, R.string.wifi_setting_sleep_policy_error,
+                    Toast.LENGTH_SHORT).show();
+            return false;
+        }
+        return true;
+    }
+
+    private void updateSleepPolicySummary(Preference sleepPolicyPref, String value) {
+        if (value != null) {
+            String[] values = mContext.getResources().getStringArray(R.array
+                    .wifi_sleep_policy_values);
+            final int summaryArrayResId = Utils.isWifiOnly(mContext)
+                    ? R.array.wifi_sleep_policy_entries_wifi_only
+                    : R.array.wifi_sleep_policy_entries;
+            String[] summaries = mContext.getResources().getStringArray(summaryArrayResId);
+            for (int i = 0; i < values.length; i++) {
+                if (value.equals(values[i])) {
+                    if (i < summaries.length) {
+                        sleepPolicyPref.setSummary(summaries[i]);
+                        return;
+                    }
+                }
+            }
+        }
+
+        sleepPolicyPref.setSummary("");
+        Log.e(TAG, "Invalid sleep policy value: " + value);
+    }
+
+}
diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider
index 05cd93b..ffaf615 100644
--- a/tests/robotests/assets/grandfather_not_implementing_index_provider
+++ b/tests/robotests/assets/grandfather_not_implementing_index_provider
@@ -2,4 +2,5 @@
 com.android.settings.language.LanguageAndRegionSettings
 com.android.settings.notification.ZenModePrioritySettings
 com.android.settings.inputmethod.InputAndGestureSettings
-com.android.settings.accounts.AccountDetailDashboardFragment
\ No newline at end of file
+com.android.settings.accounts.AccountDetailDashboardFragment
+com.android.settings.wifi.ConfigureWifiSettings
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java
index 398e9c5..78d6698 100644
--- a/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java
@@ -20,13 +20,12 @@
 import android.accounts.AuthenticatorDescription;
 import android.content.Context;
 import android.content.pm.UserInfo;
-import android.os.UserManager;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.v14.preference.PreferenceFragment;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceGroup;
 import android.support.v7.preference.PreferenceScreen;
-import android.support.v14.preference.PreferenceFragment;
-import android.util.SparseArray;
 
 import com.android.settings.AccessiblePreferenceCategory;
 import com.android.settings.R;
@@ -37,8 +36,6 @@
 import com.android.settings.testutils.shadow.ShadowAccountManager;
 import com.android.settings.testutils.shadow.ShadowContentResolver;
 
-import java.util.ArrayList;
-import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,6 +44,9 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowApplication;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Answers.RETURNS_DEEP_STUBS;
 import static org.mockito.Matchers.any;
@@ -208,6 +208,20 @@
     }
 
     @Test
+    @Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class})
+    public void onResume_noPreferenceManager_shouldNotCrash() {
+        when(mFragment.getPreferenceManager()).thenReturn(null);
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", 0));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.isLinkedUser()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+        mController.onResume();
+
+        // Should not crash
+    }
+
+    @Test
     public void updateRawDataToIndex_ManagedProfile_shouldNotUpdate() {
         final List<SearchIndexableRaw> data = new ArrayList<>();
         when(mUserManager.isManagedProfile()).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
index 1862dd0..b0c0b44 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
@@ -17,63 +17,188 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Fragment;
 import android.content.Context;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceViewHolder;
+import android.content.Intent;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.os.storage.VolumeInfo;
+import android.provider.DocumentsContract;
+import android.support.v7.preference.PreferenceScreen;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.LinearLayout;
 
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
 import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.SubSettings;
 import com.android.settings.TestConfig;
-import com.android.settings.deviceinfo.StorageItemPreference;
+import com.android.settings.applications.ManageApplications;
+import com.android.settingslib.deviceinfo.StorageMeasurement;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
 
 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.Mockito;
+import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
+import java.util.HashMap;
+
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class StorageItemPreferenceControllerTest {
-    private static final String KEY = "pref";
+    /**
+     *  In O, this will change to 1000 instead of 1024 due to the formatter properly defining a
+     *  kilobyte.
+     */
+    private static long KILOBYTE = 1024L;
+
     private Context mContext;
+    private VolumeInfo mVolume;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Fragment mFragment;
+    @Mock
+    private StorageVolumeProvider mSvp;
     private StorageItemPreferenceController mController;
-    private PreferenceViewHolder mHolder;
     private StorageItemPreferenceAlternate mPreference;
 
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mVolume = new VolumeInfo("id", 0, null, "id");
         mContext = RuntimeEnvironment.application;
-        mController = new StorageItemPreferenceController(mContext, KEY);
+        // Note: null is passed as the Lifecycle because we are handling it outside of the normal
+        //       Settings fragment lifecycle for test purposes.
+        mController = new StorageItemPreferenceController(mContext, null, mFragment, mVolume, mSvp);
         mPreference = new StorageItemPreferenceAlternate(mContext);
 
         // Inflate the preference and the widget.
         LayoutInflater inflater = LayoutInflater.from(mContext);
-        final View view = inflater.inflate(mPreference.getLayoutResource(),
-                new LinearLayout(mContext), false);
-
-        mHolder = new PreferenceViewHolder(view);
-    }
-
-    @Test
-    public void testGetKey() {
-        assertThat(mController.getPreferenceKey()).isEqualTo(KEY);
+        final View view = inflater.inflate(
+                mPreference.getLayoutResource(), new LinearLayout(mContext), false);
     }
 
     @Test
     public void testUpdateStateWithInitialState() {
-        mController.updateState(mPreference);
-        assertThat(mPreference.getSummary().toString()).isEqualTo("Calculating…");
+        assertThat(mPreference.getSummary().toString()).isEqualTo(
+                mContext.getString(R.string.memory_calculating_size));
     }
 
     @Test
-    public void testPreferenceShouldUpdateAfterPopulatingData() {
-        mController.setStorageSize(1024L);
-        mController.updateState(mPreference);
-        assertThat(mPreference.getSummary().toString()).isEqualTo("1.00KB");
+    public void testClickPhotos() {
+        mPreference.setKey("pref_photos_videos");
+        mController.handlePreferenceTreeClick(mPreference);
 
+        final ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mFragment.getActivity()).startActivityAsUser(argumentCaptor.capture(),
+                any(UserHandle.class));
+
+        Intent intent = argumentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(DocumentsContract.ACTION_BROWSE);
+        assertThat(intent.getData()).isEqualTo(DocumentsContract.buildRootUri(
+                "com.android.providers.media.documents",
+                "images_root"));
+    }
+
+    @Test
+    public void testClickAudio() {
+        mPreference.setKey("pref_music_audio");
+        mController.handlePreferenceTreeClick(mPreference);
+
+        final ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mFragment.getActivity()).startActivityAsUser(argumentCaptor.capture(),
+                any(UserHandle.class));
+
+        Intent intent = argumentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(DocumentsContract.ACTION_BROWSE);
+        assertThat(intent.getData()).isEqualTo(DocumentsContract.buildRootUri(
+                "com.android.providers.media.documents",
+                "audio_root"));
+    }
+
+    @Test
+    public void testClickApps() {
+        mPreference.setKey("pref_other_apps");
+        mController.handlePreferenceTreeClick(mPreference);
+
+        final ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mFragment.getActivity()).startActivityAsUser(argumentCaptor.capture(),
+                any(UserHandle.class));
+
+        Intent intent = argumentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_MAIN);
+        assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
+        assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo(
+                ManageApplications.class.getName());
+    }
+
+    @Test
+    public void testClickFiles() {
+        when(mSvp.findEmulatedForPrivate(any(VolumeInfo.class))).thenReturn(mVolume);
+        mPreference.setKey("pref_files");
+        mController.handlePreferenceTreeClick(mPreference);
+
+        final ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mFragment.getActivity()).startActivityAsUser(argumentCaptor.capture(),
+                any(UserHandle.class));
+
+        Intent intent = argumentCaptor.getValue();
+        Intent browseIntent = mVolume.buildBrowseIntent();
+        assertThat(intent.getAction()).isEqualTo(browseIntent.getAction());
+        assertThat(intent.getData()).isEqualTo(browseIntent.getData());
+    }
+
+    @Test
+    public void testMeasurementCompletedUpdatesPreferences() {
+        StorageItemPreferenceAlternate audio = new StorageItemPreferenceAlternate(mContext);
+        StorageItemPreferenceAlternate image = new StorageItemPreferenceAlternate(mContext);
+        StorageItemPreferenceAlternate games = new StorageItemPreferenceAlternate(mContext);
+        StorageItemPreferenceAlternate apps = new StorageItemPreferenceAlternate(mContext);
+        StorageItemPreferenceAlternate system = new StorageItemPreferenceAlternate(mContext);
+        StorageItemPreferenceAlternate files = new StorageItemPreferenceAlternate(mContext);
+        PreferenceScreen screen = mock(PreferenceScreen.class);
+        when(screen.findPreference(
+                Mockito.eq(StorageItemPreferenceController.AUDIO_KEY))).thenReturn(audio);
+        when(screen.findPreference(
+                Mockito.eq(StorageItemPreferenceController.PHOTO_KEY))).thenReturn(image);
+        when(screen.findPreference(
+                Mockito.eq(StorageItemPreferenceController.GAME_KEY))).thenReturn(games);
+        when(screen.findPreference(
+                Mockito.eq(StorageItemPreferenceController.OTHER_APPS_KEY))).thenReturn(apps);
+        when(screen.findPreference(
+                Mockito.eq(StorageItemPreferenceController.SYSTEM_KEY))).thenReturn(system);
+        when(screen.findPreference(
+                Mockito.eq(StorageItemPreferenceController.FILES_KEY))).thenReturn(files);
+        mController.displayPreference(screen);
+
+        StorageMeasurement.MeasurementDetails details = new StorageMeasurement.MeasurementDetails();
+        details.appsSize.put(0, KILOBYTE);
+        HashMap<String, Long> mediaSizes = new HashMap<>();
+        mediaSizes.put(Environment.DIRECTORY_PICTURES, KILOBYTE * 2);
+        mediaSizes.put(Environment.DIRECTORY_MOVIES, KILOBYTE * 3);
+        mediaSizes.put(Environment.DIRECTORY_MUSIC, KILOBYTE * 4);
+        mediaSizes.put(Environment.DIRECTORY_DOWNLOADS, KILOBYTE * 5);
+        details.mediaSize.put(0, mediaSizes);
+        mController.setSystemSize(KILOBYTE * 6);
+        mController.onDetailsChanged(details);
+
+        assertThat(audio.getSummary().toString()).isEqualTo("4.00KB");
+        assertThat(image.getSummary().toString()).isEqualTo("5.00KB");
+        assertThat(games.getSummary().toString()).isEqualTo("0");
+        assertThat(apps.getSummary().toString()).isEqualTo("1.00KB");
+        assertThat(system.getSummary().toString()).isEqualTo("6.00KB");
+        assertThat(files.getSummary().toString()).isEqualTo("5.00KB");
     }
 }
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.java
new file mode 100644
index 0000000..848616a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.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.deviceinfo.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.storage.VolumeInfo;
+import android.view.LayoutInflater;
+import android.widget.LinearLayout;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.io.File;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class StorageSummaryDonutPreferenceControllerTest {
+    private static String KEY = "pref";
+
+    private Context mContext;
+    private StorageSummaryDonutPreferenceController mController;
+    private StorageSummaryDonutPreference mPreference;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = RuntimeEnvironment.application;
+        mController = new StorageSummaryDonutPreferenceController(mContext);
+        mPreference = new StorageSummaryDonutPreference(mContext);
+
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        inflater.inflate(mPreference.getLayoutResource(), new LinearLayout(mContext), false);
+    }
+
+    @Test
+    public void testEmpty() throws Exception {
+        mController.updateBytes(0, 0);
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getTitle().toString()).isEqualTo("0.00B used");
+        assertThat(mPreference.getSummary().toString()).isEqualTo("0.00B free");
+    }
+
+    @Test
+    public void testUsedStorage() throws Exception {
+        mController.updateBytes(1024, 1024 * 10);
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getTitle().toString()).isEqualTo("1.00KB used");
+        assertThat(mPreference.getSummary().toString()).isEqualTo("9.00KB free");
+    }
+
+    @Test
+    public void testPopulateWithVolume() throws Exception {
+        VolumeInfo volume = Mockito.mock(VolumeInfo.class);
+        File file = Mockito.mock(File.class);
+        StorageVolumeProvider svp = Mockito.mock(StorageVolumeProvider.class);
+        when(volume.getPath()).thenReturn(file);
+        when(file.getTotalSpace()).thenReturn(1024L * 10);
+        when(file.getFreeSpace()).thenReturn(1024L);
+        when(svp.getPrimaryStorageSize()).thenReturn(1024L * 10);
+
+        mController.updateSizes(svp, volume);
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getTitle().toString()).isEqualTo("9.00KB used");
+        assertThat(mPreference.getSummary().toString()).isEqualTo("1.00KB free");
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search/DynamicIndexableContentMonitorTest.java b/tests/robotests/src/com/android/settings/search/DynamicIndexableContentMonitorTest.java
index 28fe8b0..58e9260 100644
--- a/tests/robotests/src/com/android/settings/search/DynamicIndexableContentMonitorTest.java
+++ b/tests/robotests/src/com/android/settings/search/DynamicIndexableContentMonitorTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.only;
@@ -50,6 +49,7 @@
 import android.print.PrintManager;
 import android.print.PrintServicesLoader;
 import android.printservice.PrintServiceInfo;
+import android.provider.Settings;
 import android.provider.UserDictionary;
 import android.view.inputmethod.InputMethodInfo;
 
@@ -57,7 +57,10 @@
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
+import com.android.settings.inputmethod.PhysicalKeyboardFragment;
+import com.android.settings.inputmethod.VirtualKeyboardFragment;
 import com.android.settings.print.PrintSettingsFragment;
 import com.android.settings.testutils.shadow.ShadowActivityWithLoadManager;
 import com.android.settings.testutils.shadow.ShadowContextImplWithRegisterReceiver;
@@ -69,7 +72,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.verification.VerificationMode;
 import org.robolectric.Robolectric;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
@@ -210,9 +212,7 @@
         mMonitor.register(mActivity, LOADER_ID, mIndex, true /* isUserUnlocked */);
 
         // Rebuild indexing should happen.
-        // CAVEAT: Currently InputMethodAndLanuageSettings may be indexed once for input devices and
-        // once for input methods.
-        verifyRebuildIndexing(InputMethodAndLanguageSettings.class, atLeastOnce());
+        verifyRebuildIndexing(PhysicalKeyboardFragment.class);
         // Input monitor should be registered to InputManager.
         final InputManager.InputDeviceListener listener = extactInputDeviceListener();
         assertThat(listener).isNotNull();
@@ -224,7 +224,7 @@
 
         mMonitor.register(mActivity, LOADER_ID, mIndex, true /* isUserUnlocked */);
 
-        verifyNoIndexing(InputMethodAndLanguageSettings.class);
+        verifyNoIndexing(PhysicalKeyboardFragment.class);
         assertThat(extactInputDeviceListener()).isEqualTo(listener);
 
         /*
@@ -234,7 +234,7 @@
 
         listener.onInputDeviceAdded(1 /* deviceId */);
 
-        verifyIncrementalIndexing(InputMethodAndLanguageSettings.class);
+        verifyIncrementalIndexing(PhysicalKeyboardFragment.class);
 
         /*
          * A device is removed.
@@ -243,7 +243,7 @@
 
         listener.onInputDeviceRemoved(2 /* deviceId */);
 
-        verifyRebuildIndexing(InputMethodAndLanguageSettings.class);
+        verifyRebuildIndexing(PhysicalKeyboardFragment.class);
 
         /*
          * A device is changed.
@@ -252,7 +252,7 @@
 
         listener.onInputDeviceChanged(3 /* deviceId */);
 
-        verifyRebuildIndexing(InputMethodAndLanguageSettings.class);
+        verifyRebuildIndexing(PhysicalKeyboardFragment.class);
     }
 
     @Test
@@ -338,9 +338,14 @@
         final PackageMonitor packageMonitor = extractPackageMonitor();
         assertThat(packageMonitor).isNotNull();
 
-        // CAVEAT: Currently InputMethodAndLanuageSettings may be indexed once for input devices and
-        // once for input methods.
-        verifyRebuildIndexing(InputMethodAndLanguageSettings.class, atLeastOnce());
+        verifyRebuildIndexing(VirtualKeyboardFragment.class);
+        verifyRebuildIndexing(AvailableVirtualKeyboardFragment.class);
+
+        final Uri enabledInputMethodsContentUri = Settings.Secure.getUriFor(
+                Settings.Secure.ENABLED_INPUT_METHODS);
+        // Content observer should be registered.
+        final ContentObserver observer = extractContentObserver(enabledInputMethodsContentUri);
+        assertThat(observer).isNotNull();
 
         /*
          * When an input method service package is installed, incremental indexing happen.
@@ -351,7 +356,8 @@
         packageMonitor.onPackageAppeared(IME_PACKAGE_1, USER_ID);
         Robolectric.flushBackgroundThreadScheduler();
 
-        verifyIncrementalIndexing(InputMethodAndLanguageSettings.class);
+        verifyIncrementalIndexing(VirtualKeyboardFragment.class);
+        verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
 
         /*
          * When another input method service package is installed, incremental indexing happens.
@@ -362,7 +368,8 @@
         packageMonitor.onPackageAppeared(IME_PACKAGE_2, USER_ID);
         Robolectric.flushBackgroundThreadScheduler();
 
-        verifyIncrementalIndexing(InputMethodAndLanguageSettings.class);
+        verifyIncrementalIndexing(VirtualKeyboardFragment.class);
+        verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
 
         /*
          * When an input method service is disabled, rebuild indexing happens.
@@ -374,7 +381,8 @@
         packageMonitor.onPackageModified(IME_PACKAGE_1);
         Robolectric.flushBackgroundThreadScheduler();
 
-        verifyRebuildIndexing(InputMethodAndLanguageSettings.class);
+        verifyRebuildIndexing(VirtualKeyboardFragment.class);
+        verifyRebuildIndexing(AvailableVirtualKeyboardFragment.class);
 
         /*
          * When an input method service is enabled, incremental indexing happens.
@@ -386,7 +394,8 @@
         packageMonitor.onPackageModified(IME_PACKAGE_1);
         Robolectric.flushBackgroundThreadScheduler();
 
-        verifyIncrementalIndexing(InputMethodAndLanguageSettings.class);
+        verifyIncrementalIndexing(VirtualKeyboardFragment.class);
+        verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
 
         /*
          * When an input method service package is uninstalled, rebuild indexing happens.
@@ -396,7 +405,8 @@
 
         packageMonitor.onPackageDisappeared(IME_PACKAGE_1, USER_ID);
 
-        verifyRebuildIndexing(InputMethodAndLanguageSettings.class);
+        verifyRebuildIndexing(VirtualKeyboardFragment.class);
+        verifyRebuildIndexing(AvailableVirtualKeyboardFragment.class);
 
         /*
          * When an accessibility service package is installed, nothing happens.
@@ -406,7 +416,18 @@
 
         packageMonitor.onPackageAppeared(A11Y_PACKAGE_1, USER_ID);
 
-        verifyNoIndexing(InputMethodAndLanguageSettings.class);
+        verifyNoIndexing(VirtualKeyboardFragment.class);
+        verifyNoIndexing(AvailableVirtualKeyboardFragment.class);
+
+        /*
+         * When enabled IMEs list is changed, rebuild indexing happens.
+         */
+        reset(mIndex);
+
+        observer.onChange(false /* selfChange */, enabledInputMethodsContentUri);
+
+        verifyRebuildIndexing(VirtualKeyboardFragment.class);
+        verifyRebuildIndexing(AvailableVirtualKeyboardFragment.class);
     }
 
     @Test
@@ -417,9 +438,7 @@
         final ContentObserver observer = extractContentObserver(UserDictionary.Words.CONTENT_URI);
         assertThat(observer).isNotNull();
 
-        // CAVEAT: Currently InputMethodAndLanuageSettings may be indexed once for input devices and
-        // once for input methods.
-        verifyRebuildIndexing(InputMethodAndLanguageSettings.class, atLeastOnce());
+        verifyRebuildIndexing(InputMethodAndLanguageSettings.class);
 
         /*
          * When user dictionary content is changed, rebuild indexing happens.
@@ -441,11 +460,7 @@
     }
 
     private void verifyRebuildIndexing(Class<?> indexingClass) {
-        verifyRebuildIndexing(indexingClass, times(1));
-    }
-
-    private void verifyRebuildIndexing(Class<?> indexingClass, VerificationMode verificationMode) {
-        verify(mIndex, verificationMode).updateFromClassNameResource(indexingClass.getName(),
+        verify(mIndex, times(1)).updateFromClassNameResource(indexingClass.getName(),
                 true /* rebuild */, true /* includeInSearchResults */);
         verify(mIndex, never()).updateFromClassNameResource(indexingClass.getName(),
                 false /* rebuild */, true /* includeInSearchResults */);
diff --git a/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java b/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
index 1847d0e..6ad7501 100644
--- a/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
+++ b/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
@@ -22,8 +22,8 @@
 import android.content.Intent;
 import android.database.MatrixCursor;
 import android.graphics.drawable.Drawable;
-
 import android.util.ArrayMap;
+
 import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.SubSettings;
@@ -40,50 +40,30 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.android.settings.search.IndexDatabaseHelper.IndexColumns;
-
 import static com.google.common.truth.Truth.assertThat;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class CursorToSearchResultConverterTest {
 
-    private CursorToSearchResultConverter mConverter;
-
-    private static final String[] COLUMNS = new String[]{
-            IndexColumns.DOCID,
-            IndexColumns.DATA_TITLE,
-            IndexColumns.DATA_SUMMARY_ON,
-            IndexColumns.DATA_SUMMARY_OFF,
-            IndexColumns.CLASS_NAME,
-            IndexColumns.SCREEN_TITLE,
-            IndexColumns.ICON,
-            IndexColumns.INTENT_ACTION,
-            IndexColumns.INTENT_TARGET_PACKAGE,
-            IndexColumns.INTENT_TARGET_CLASS,
-            IndexColumns.DATA_KEY_REF,
-            IndexColumns.PAYLOAD_TYPE,
-            IndexColumns.PAYLOAD
-    };
-
     private static final String ID = "id";
     private static final String[] TITLES = {"title1", "title2", "title3"};
     private static final String SUMMARY = "summary";
+    private static final String TARGET_PACKAGE = "a.b.c";
+    private static final String TARGET_CLASS = "a.b.c.class";
     private static final String QUERY = "query";
-    private final int BASE_RANK = 1;
+    private static final Intent INTENT = new Intent("com.android.settings");
+    private static final int ICON = R.drawable.ic_search_history;
+    private static final int BASE_RANK = 1;
     private static final int EXAMPLES = 3;
 
-    private static final Intent mIntent = new Intent("com.android.settings");
-    private static final int mIcon = R.drawable.ic_search_history;
-    private List<SearchResult> mResults;
-
     private Drawable mDrawable;
+    private CursorToSearchResultConverter mConverter;
 
     @Before
     public void setUp() {
         Context context = Robolectric.buildActivity(Activity.class).get();
-        mDrawable = context.getDrawable(mIcon);
-        mResults = new ArrayList<>();
+        mDrawable = context.getDrawable(ICON);
         mConverter = new CursorToSearchResultConverter(context, QUERY);
     }
 
@@ -128,6 +108,7 @@
         List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             Drawable resultDrawable = results.get(i).icon;
+            assertThat(resultDrawable).isNotNull();
             assertThat(resultDrawable.toString()).isEqualTo(mDrawable.toString());
         }
     }
@@ -154,7 +135,7 @@
 
     @Test
     public void testParseCursor_MatchesIntentForSubSettings() {
-        MatrixCursor cursor = new MatrixCursor(COLUMNS);
+        MatrixCursor cursor = new MatrixCursor(DatabaseResultLoader.SELECT_COLUMNS);
         final String BLANK = "";
         cursor.addRow(new Object[]{
                 ID,      // Doc ID
@@ -184,13 +165,39 @@
         for (int i = 0; i < EXAMPLES; i++) {
             payload = (IntentPayload) results.get(i).payload;
             Intent intent = payload.intent;
-            assertThat(intent.getAction()).isEqualTo(mIntent.getAction());
+            assertThat(intent.getAction()).isEqualTo(INTENT.getAction());
         }
     }
 
     @Test
+    public void testParseCursor_MatchesIntentPayloadForExternalApps() {
+        MatrixCursor cursor = new MatrixCursor(DatabaseResultLoader.SELECT_COLUMNS);
+        cursor.addRow(new Object[]{
+                ID,      // Doc ID
+                TITLES[0], // Title
+                SUMMARY, // Summary on
+                SUMMARY, // summary off
+                null,    // class
+                TITLES[0], // Title
+                null,    // icon
+                Intent.ACTION_VIEW,   // action
+                TARGET_PACKAGE,    // target package
+                TARGET_CLASS,   // target class
+                QUERY,   // Key
+                PayloadType.INTENT,    // Payload Type
+                null // Payload
+        });
+        List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK);
+        IntentPayload payload = (IntentPayload) results.get(0).payload;
+        Intent intent = payload.intent;
+
+        assertThat(intent.getComponent().getPackageName()).isEqualTo(TARGET_PACKAGE);
+        assertThat(intent.getComponent().getClassName()).isEqualTo(TARGET_CLASS);
+    }
+
+    @Test
     public void testParseCursor_MatchesInlineSwitchPayload() {
-        MatrixCursor cursor = new MatrixCursor(COLUMNS);
+        MatrixCursor cursor = new MatrixCursor(DatabaseResultLoader.SELECT_COLUMNS);
         final String BLANK = "";
         final String uri = "test.com";
         final int type = ResultPayload.PayloadType.INLINE_SWITCH;
@@ -230,19 +237,19 @@
     }
 
     private MatrixCursor getDummyCursor(boolean hasIcon) {
-        MatrixCursor cursor = new MatrixCursor(COLUMNS);
+        MatrixCursor cursor = new MatrixCursor(DatabaseResultLoader.SELECT_COLUMNS);
         final String BLANK = "";
 
         for (int i = 0; i < EXAMPLES; i++) {
-            ArrayList<String> item = new ArrayList<>(COLUMNS.length);
+            ArrayList<String> item = new ArrayList<>(DatabaseResultLoader.SELECT_COLUMNS.length);
             item.add(ID + i); // Doc ID
             item.add(TITLES[i]); // Title
             item.add(SUMMARY); // Summary on
             item.add(BLANK); // summary off
             item.add(BLANK); // classname
             item.add(BLANK); // screen title
-            item.add(hasIcon ? Integer.toString(mIcon) : null); // Icon
-            item.add(mIntent.getAction()); // Intent action
+            item.add(hasIcon ? Integer.toString(ICON) : null); // Icon
+            item.add(INTENT.getAction()); // Intent action
             item.add(BLANK); // target package
             item.add(BLANK); // target class
             item.add(BLANK); // Key
diff --git a/tests/robotests/src/com/android/settings/wifi/AllowRecommendationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/AllowRecommendationPreferenceControllerTest.java
new file mode 100644
index 0000000..d33854a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/AllowRecommendationPreferenceControllerTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.wifi;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+
+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 static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AllowRecommendationPreferenceControllerTest {
+
+    private Context mContext;
+    private AllowRecommendationPreferenceController mController;
+
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mController = new AllowRecommendationPreferenceController(mContext);
+    }
+
+    @Test
+    public void testIsAvailable_shouldAlwaysReturnTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void handlePreferenceTreeClick_nonMatchingKey_shouldDoNothing() {
+        final SwitchPreference pref = new SwitchPreference(mContext);
+
+        assertThat(mController.handlePreferenceTreeClick(pref)).isFalse();
+    }
+
+    @Test
+    public void handlePreferenceTreeClick_nonMatchingType_shouldDoNothing() {
+        final Preference pref = new Preference(mContext);
+        pref.setKey(mController.getPreferenceKey());
+
+        assertThat(mController.handlePreferenceTreeClick(pref)).isFalse();
+    }
+
+
+    @Test
+    public void handlePreferenceTreeClick_matchingKeyAndType_shouldUpdateSetting() {
+        final SwitchPreference pref = new SwitchPreference(mContext);
+        pref.setKey(mController.getPreferenceKey());
+
+        // Turn on
+        pref.setChecked(true);
+        assertThat(mController.handlePreferenceTreeClick(pref)).isTrue();
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0))
+                .isEqualTo(1);
+
+        // Turn off
+        pref.setChecked(false);
+        assertThat(mController.handlePreferenceTreeClick(pref)).isTrue();
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0))
+                .isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/CellularFallbackPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/CellularFallbackPreferenceControllerTest.java
new file mode 100644
index 0000000..bf564a5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/CellularFallbackPreferenceControllerTest.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.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+
+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.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class CellularFallbackPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private WifiManager mWifiManager;
+
+    private CellularFallbackPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mController = new CellularFallbackPreferenceController(mContext);
+    }
+
+    @Test
+    public void isAvailable_avoidBadWifiConfigIsFalse_shouldReturnTrue() {
+        when(mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_networkAvoidBadWifi))
+                .thenReturn(0);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_avoidBadWifiConfigIsTrue_shouldReturnFalse() {
+        when(mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_networkAvoidBadWifi))
+                .thenReturn(1);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/NotifyOpenNetworkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/NotifyOpenNetworkPreferenceControllerTest.java
new file mode 100644
index 0000000..aa12787
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/NotifyOpenNetworkPreferenceControllerTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+
+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.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class NotifyOpenNetworkPreferenceControllerTest {
+
+    @Mock
+    private WifiManager mWifiManager;
+    private Context mContext;
+    private NotifyOpenNetworksPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mController = new NotifyOpenNetworksPreferenceController(mContext, mWifiManager);
+    }
+
+    @Test
+    public void testIsAvailable_shouldAlwaysReturnTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void handlePreferenceTreeClick_nonMatchingKey_shouldDoNothing() {
+        final SwitchPreference pref = new SwitchPreference(mContext);
+
+        assertThat(mController.handlePreferenceTreeClick(pref)).isFalse();
+    }
+
+    @Test
+    public void handlePreferenceTreeClick_nonMatchingType_shouldDoNothing() {
+        final Preference pref = new Preference(mContext);
+        pref.setKey(mController.getPreferenceKey());
+
+        assertThat(mController.handlePreferenceTreeClick(pref)).isFalse();
+    }
+
+
+    @Test
+    public void handlePreferenceTreeClick_matchingKeyAndType_shouldUpdateSetting() {
+        final SwitchPreference pref = new SwitchPreference(mContext);
+        pref.setChecked(true);
+        pref.setKey(mController.getPreferenceKey());
+
+        assertThat(mController.handlePreferenceTreeClick(pref)).isTrue();
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0))
+                .isEqualTo(1);
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/SavedNetworkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/SavedNetworkPreferenceControllerTest.java
new file mode 100644
index 0000000..657c21e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/SavedNetworkPreferenceControllerTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+
+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.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+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 SavedNetworkPreferenceControllerTest {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private WifiManager mWifiManager;
+
+    private SavedNetworkPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mController = new SavedNetworkPreferenceController(mContext, mWifiManager);
+    }
+
+    @Test
+    public void isAvailable_noSavedNetwork_shouldReturnFalse() {
+        when(mWifiManager.getConfiguredNetworks()).thenReturn(null);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_hasSavedNetwork_shouldReturnTrue() {
+        List<WifiConfiguration> configs = mock(List.class);
+        when(configs.isEmpty()).thenReturn(false);
+        when(mWifiManager.getConfiguredNetworks()).thenReturn(configs);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiInfoPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/WifiInfoPreferenceControllerTest.java
new file mode 100644
index 0000000..40c480f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/WifiInfoPreferenceControllerTest.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.wifi;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.net.wifi.WifiManager;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+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 static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+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 WifiInfoPreferenceControllerTest {
+
+    @Mock
+    private Context mContext;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private WifiManager mWifiManager;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Preference mIpPreference;
+    @Mock
+    private Preference mMacPreference;
+
+    private Lifecycle mLifecycle;
+    private WifiInfoPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLifecycle = new Lifecycle();
+        when(mContext.getSystemService(WifiManager.class))
+                .thenReturn(mWifiManager);
+        when(mScreen.findPreference(anyString()))
+                .thenReturn(mMacPreference)
+                .thenReturn(mIpPreference);
+        mController = new WifiInfoPreferenceController(mContext, mLifecycle, mWifiManager);
+    }
+
+    @Test
+    public void testIsAvailable_shouldAlwaysReturnTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void getPreferenceKey_shouldReturnNull() {
+        assertThat(mController.getPreferenceKey()).isNull();
+    }
+
+    @Test
+    public void runThroughLifecycle_shouldInstallListenerOnResume() {
+        mLifecycle.onResume();
+        verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
+
+        mLifecycle.onPause();
+        verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+    }
+
+    @Test
+    public void onResume_shouldUpdateWifiInfo() {
+        when(mWifiManager.getCurrentNetwork()).thenReturn(null);
+
+        mController.displayPreference(mScreen);
+        mController.onResume();
+
+        verify(mMacPreference).setSummary(any());
+        verify(mIpPreference).setSummary(any());
+    }
+}