Merge "Fixing issue with PiP settings not showing apps for other profiles."
diff --git a/res/layout/choose_lock_pattern_common.xml b/res/layout/choose_lock_pattern_common.xml
index 65135dd..949d130 100644
--- a/res/layout/choose_lock_pattern_common.xml
+++ b/res/layout/choose_lock_pattern_common.xml
@@ -24,7 +24,7 @@
     android:icon="@drawable/ic_lock"
     android:layout="@layout/suw_glif_blank_template"
     settings:suwFooter="@layout/choose_lock_pattern_common_footer"
-    settings:suwHeaderText="@string/lockpassword_choose_your_pattern_header">
+    settings:suwHeaderText="@string/lockpassword_choose_your_screen_lock_header">
 
     <com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
         android:id="@+id/topLayout"
diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java
index b06c070..ddf68c1 100644
--- a/src/com/android/settings/applications/AppInfoBase.java
+++ b/src/com/android/settings/applications/AppInfoBase.java
@@ -217,7 +217,9 @@
 
     @Override
     public void onPackageListChanged() {
-        refreshUi();
+        if (!refreshUi()) {
+            setIntentAndFinish(true, true);
+        }
     }
 
     public static void startAppInfoFragment(Class<?> fragment, int titleRes,
diff --git a/src/com/android/settings/applications/UsageAccessDetails.java b/src/com/android/settings/applications/UsageAccessDetails.java
index e40ae37..253ddfd 100644
--- a/src/com/android/settings/applications/UsageAccessDetails.java
+++ b/src/com/android/settings/applications/UsageAccessDetails.java
@@ -137,6 +137,9 @@
 
     @Override
     protected boolean refreshUi() {
+        if (mPackageInfo == null) {
+            return false;
+        }
         mUsageState = mUsageBridge.getUsageInfo(mPackageName,
                 mPackageInfo.applicationInfo.uid);
 
diff --git a/src/com/android/settings/bluetooth/BluetoothSummaryUpdater.java b/src/com/android/settings/bluetooth/BluetoothSummaryUpdater.java
index 7d2cc18..662cd70 100644
--- a/src/com/android/settings/bluetooth/BluetoothSummaryUpdater.java
+++ b/src/com/android/settings/bluetooth/BluetoothSummaryUpdater.java
@@ -42,31 +42,21 @@
     private final LocalBluetoothManager mBluetoothManager;
     private final LocalBluetoothAdapter mBluetoothAdapter;
 
-    private boolean mEnabled;
-    private int mConnectionState;
-
     public BluetoothSummaryUpdater(Context context, OnSummaryChangeListener listener,
             LocalBluetoothManager bluetoothManager) {
         super(context, listener);
         mBluetoothManager = bluetoothManager;
         mBluetoothAdapter = mBluetoothManager != null
-            ? mBluetoothManager.getBluetoothAdapter() : null;
+                ? mBluetoothManager.getBluetoothAdapter() : null;
     }
 
     @Override
     public void onBluetoothStateChanged(int bluetoothState) {
-        mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
-            || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
-        if (!mEnabled) {
-            mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
-        }
         notifyChangeIfNeeded();
     }
 
     @Override
     public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-        mConnectionState = state;
-        updateConnected();
         notifyChangeIfNeeded();
     }
 
@@ -92,8 +82,6 @@
             return;
         }
         if (listening) {
-            mEnabled = mBluetoothAdapter.isEnabled();
-            mConnectionState = mBluetoothAdapter.getConnectionState();
             notifyChangeIfNeeded();
             mBluetoothManager.getEventManager().registerCallback(this);
         } else {
@@ -103,10 +91,10 @@
 
     @Override
     public String getSummary() {
-        if (!mEnabled) {
+        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
             return mContext.getString(R.string.bluetooth_disabled);
         }
-        switch (mConnectionState) {
+        switch (mBluetoothAdapter.getConnectionState()) {
             case BluetoothAdapter.STATE_CONNECTED:
                 return getConnectedDeviceSummary();
             case BluetoothAdapter.STATE_CONNECTING:
@@ -118,50 +106,17 @@
         }
     }
 
-    private void updateConnected() {
-        if (mBluetoothAdapter == null) {
-            return;
-        }
-        // Make sure our connection state is up to date.
-        int state = mBluetoothAdapter.getConnectionState();
-        if (state != mConnectionState) {
-            mConnectionState = state;
-            return;
-        }
-        final Collection<CachedBluetoothDevice> devices = getDevices();
-        if (devices == null) {
-            mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
-            return;
-        }
-        if (mConnectionState == BluetoothAdapter.STATE_CONNECTED) {
-            CachedBluetoothDevice connectedDevice = null;
-            for (CachedBluetoothDevice device : devices) {
-                if (device.isConnected()) {
-                    connectedDevice = device;
-                    break;
-                }
-            }
-            if (connectedDevice == null) {
-                // If somehow we think we are connected, but have no connected devices, we
-                // aren't connected.
-                mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
-            }
-        }
-    }
-
-    private Collection<CachedBluetoothDevice> getDevices() {
-        return mBluetoothManager != null
-            ? mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
-            : null;
-    }
-
     @VisibleForTesting
     String getConnectedDeviceSummary() {
         String deviceName = null;
         int count = 0;
         final Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
-        if (devices == null || devices.isEmpty()) {
-            return null;
+        if (devices == null) {
+            Log.e(TAG, "getConnectedDeviceSummary, bonded devices are null");
+            return mContext.getString(R.string.bluetooth_disabled);
+        } else if (devices.isEmpty()) {
+            Log.e(TAG, "getConnectedDeviceSummary, no bonded devices");
+            return mContext.getString(R.string.disconnected);
         }
         for (BluetoothDevice device : devices) {
             if (device.isConnected()) {
@@ -173,12 +128,13 @@
             }
         }
         if (deviceName == null) {
-            Log.w(TAG, "getConnectedDeviceSummary, deviceName is null, numBondedDevices="
+            Log.e(TAG, "getConnectedDeviceSummary, deviceName is null, numBondedDevices="
                     + devices.size());
             for (BluetoothDevice device : devices) {
-                Log.w(TAG, "getConnectedDeviceSummary, device=" + device.getName() + "["
+                Log.e(TAG, "getConnectedDeviceSummary, device=" + device.getName() + "["
                         + device.getAddress() + "]" + ", isConnected=" + device.isConnected());
             }
+            return mContext.getString(R.string.disconnected);
         }
         return count > 1 ? mContext.getString(R.string.bluetooth_connected_multiple_devices_summary)
                 : mContext.getString(R.string.bluetooth_connected_summary, deviceName);
diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
index bfb69e7..a5d0715 100644
--- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java
+++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
@@ -65,4 +65,8 @@
     protected final Context getPrefContext() {
         return getPreferenceManager().getContext();
     }
+
+    protected final VisibilityLoggerMixin getVisibilityLogger() {
+        return mVisibilityLoggerMixin;
+    }
 }
diff --git a/src/com/android/settings/core/instrumentation/EventLogWriter.java b/src/com/android/settings/core/instrumentation/EventLogWriter.java
index e7628e8..3196f76 100644
--- a/src/com/android/settings/core/instrumentation/EventLogWriter.java
+++ b/src/com/android/settings/core/instrumentation/EventLogWriter.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.metrics.LogMaker;
+import android.util.Log;
 import android.util.Pair;
 
 import com.android.internal.logging.MetricsLogger;
@@ -28,6 +29,8 @@
  */
 public class EventLogWriter implements LogWriter {
 
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
+
     public void visible(Context context, int source, int category) {
         final LogMaker logMaker = new LogMaker(category)
                 .setType(MetricsProto.MetricsEvent.TYPE_OPEN)
@@ -39,6 +42,24 @@
         MetricsLogger.hidden(context, category);
     }
 
+    public void action(int category, int value, Pair<Integer, Object>... taggedData) {
+        if (taggedData == null || taggedData.length == 0) {
+            mMetricsLogger.action(category, value);
+        } else {
+            final LogMaker logMaker = new LogMaker(category)
+                    .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
+                    .setSubtype(value);
+            for (Pair<Integer, Object> pair : taggedData) {
+                logMaker.addTaggedData(pair.first, pair.second);
+            }
+            mMetricsLogger.write(logMaker);
+        }
+    }
+
+    public void action(int category, boolean value, Pair<Integer, Object>... taggedData) {
+        action(category, value ? 1 : 0, taggedData);
+    }
+
     public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
         action(context, category, "", taggedData);
     }
@@ -52,12 +73,16 @@
         MetricsLogger.action(logMaker);
     }
 
+    /** @deprecated use {@link #action(int, int, Pair[])} */
+    @Deprecated
     public void action(Context context, int category, int value) {
-        MetricsLogger.action(context, category, Integer.toString(value));
+        MetricsLogger.action(context, category, value);
     }
 
+    /** @deprecated use {@link #action(int, boolean, Pair[])} */
+    @Deprecated
     public void action(Context context, int category, boolean value) {
-        MetricsLogger.action(context, category, Boolean.toString(value));
+        MetricsLogger.action(context, category, value);
     }
 
     public void action(Context context, int category, String pkg,
diff --git a/src/com/android/settings/core/instrumentation/LogWriter.java b/src/com/android/settings/core/instrumentation/LogWriter.java
index 584217d..062d46f 100644
--- a/src/com/android/settings/core/instrumentation/LogWriter.java
+++ b/src/com/android/settings/core/instrumentation/LogWriter.java
@@ -34,6 +34,16 @@
     void hidden(Context context, int category);
 
     /**
+     * Logs a user action.
+     */
+    void action(int category, int value, Pair<Integer, Object>... taggedData);
+
+    /**
+     * Logs a user action.
+     */
+    void action(int category, boolean value, Pair<Integer, Object>... taggedData);
+
+    /**
      * Logs an user action.
      */
     void action(Context context, int category, Pair<Integer, Object>... taggedData);
@@ -45,12 +55,16 @@
 
     /**
      * Logs an user action.
+     * @deprecated use {@link #action(int, int, Pair[])}
      */
+    @Deprecated
     void action(Context context, int category, int value);
 
     /**
      * Logs an user action.
+     * @deprecated use {@link #action(int, boolean, Pair[])}
      */
+    @Deprecated
     void action(Context context, int category, boolean value);
 
     /**
diff --git a/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java b/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java
index afdec55..532ec66 100644
--- a/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java
+++ b/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java
@@ -21,7 +21,7 @@
 import android.text.TextUtils;
 import android.util.Pair;
 
-import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -60,18 +60,44 @@
         }
     }
 
+    /**
+     * Logs a user action. Includes the elapsed time since the containing
+     * fragment has been visible.
+     */
+    public void action(VisibilityLoggerMixin visibilityLogger, int category, int value) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.action(category, value,
+                    sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible()));
+        }
+    }
+
+    /**
+     * Logs a user action. Includes the elapsed time since the containing
+     * fragment has been visible.
+     */
+    public void action(VisibilityLoggerMixin visibilityLogger, int category, boolean value) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.action(category, value,
+                    sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible()));
+        }
+    }
+
     public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
         for (LogWriter writer : mLoggerWriters) {
             writer.action(context, category, taggedData);
         }
     }
 
+    /** @deprecated use {@link #action(VisibilityLoggerMixin, int, int)} */
+    @Deprecated
     public void action(Context context, int category, int value) {
         for (LogWriter writer : mLoggerWriters) {
             writer.action(context, category, value);
         }
     }
 
+    /** @deprecated use {@link #action(VisibilityLoggerMixin, int, boolean)} */
+    @Deprecated
     public void action(Context context, int category, boolean value) {
         for (LogWriter writer : mLoggerWriters) {
             writer.action(context, category, value);
@@ -99,7 +125,7 @@
 
     public int getMetricsCategory(Object object) {
         if (object == null || !(object instanceof Instrumentable)) {
-            return MetricsProto.MetricsEvent.VIEW_UNKNOWN;
+            return MetricsEvent.VIEW_UNKNOWN;
         }
         return ((Instrumentable) object).getMetricsCategory();
     }
@@ -116,15 +142,19 @@
                 // Not loggable
                 return;
             }
-            action(context, MetricsProto.MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action,
-                    Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
+            action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action,
+                    Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
             return;
         } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) {
             // Going to a Setting internal page, skip click logging in favor of page's own
             // visibility logging.
             return;
         }
-        action(context, MetricsProto.MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(),
-                Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
+        action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(),
+                Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
+    }
+
+    private Pair<Integer, Object> sinceVisibleTaggedData(long timestamp) {
+        return Pair.create(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, timestamp);
     }
 }
diff --git a/src/com/android/settings/core/instrumentation/SettingSuggestionsLogWriter.java b/src/com/android/settings/core/instrumentation/SettingSuggestionsLogWriter.java
index bbdf8c9..697f0a3 100644
--- a/src/com/android/settings/core/instrumentation/SettingSuggestionsLogWriter.java
+++ b/src/com/android/settings/core/instrumentation/SettingSuggestionsLogWriter.java
@@ -46,6 +46,14 @@
     }
 
     @Override
+    public void action(int category, int value, Pair<Integer, Object>... taggedData) {
+    }
+
+    @Override
+    public void action(int category, boolean value, Pair<Integer, Object>... taggedData) {
+    }
+
+    @Override
     public void action(Context context, int category, int value) {
     }
 
diff --git a/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java b/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java
index 8de35ad..2fe2a3b 100644
--- a/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java
+++ b/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 
+import android.os.SystemClock;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.SettingsActivity;
 import com.android.settings.overlay.FeatureFactory;
@@ -41,6 +42,7 @@
 
     private MetricsFeatureProvider mMetricsFeature;
     private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN;
+    private long mVisibleTimestamp;
 
     public VisibilityLoggerMixin(int metricsCategory) {
         // MetricsFeature will be set during onAttach.
@@ -59,6 +61,7 @@
 
     @Override
     public void onResume() {
+        mVisibleTimestamp = SystemClock.elapsedRealtime();
         if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
             mMetricsFeature.visible(null /* context */, mSourceMetricsCategory, mMetricsCategory);
         }
@@ -66,6 +69,7 @@
 
     @Override
     public void onPause() {
+        mVisibleTimestamp = 0;
         if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
             mMetricsFeature.hidden(null /* context */, mMetricsCategory);
         }
@@ -85,4 +89,12 @@
         mSourceMetricsCategory = intent.getIntExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY,
                 MetricsProto.MetricsEvent.VIEW_UNKNOWN);
     }
+
+    /** Returns elapsed time since onResume() */
+    public long elapsedTimeSinceVisible() {
+        if (mVisibleTimestamp == 0) {
+            return 0;
+        }
+        return SystemClock.elapsedRealtime() - mVisibleTimestamp;
+    }
 }
diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java
index 741067a..5df66f1 100644
--- a/src/com/android/settings/dashboard/DashboardAdapter.java
+++ b/src/com/android/settings/dashboard/DashboardAdapter.java
@@ -23,6 +23,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.util.DiffUtil;
 import android.support.v7.widget.LinearLayoutManager;
@@ -62,6 +63,7 @@
         implements SummaryLoader.SummaryConsumer {
     public static final String TAG = "DashboardAdapter";
     private static final String STATE_SUGGESTION_LIST = "suggestion_list";
+    private static final String STATE_SUGGESTION_LIST_V2 = "suggestion_list_v2";
     private static final String STATE_CATEGORY_LIST = "category_list";
     private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
 
@@ -99,7 +101,10 @@
     public DashboardAdapter(Context context, Bundle savedInstanceState,
             List<Condition> conditions, SuggestionParser suggestionParser,
             SuggestionDismissController.Callback callback) {
+
+        // @deprecated In favor of suggestionsV2 below.
         List<Tile> suggestions = null;
+        List<Suggestion> suggestionsV2 = null;
         DashboardCategory category = null;
         int suggestionConditionMode = DashboardData.HEADER_MODE_DEFAULT;
 
@@ -116,6 +121,7 @@
 
         if (savedInstanceState != null) {
             suggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
+            suggestionsV2 = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST_V2);
             category = savedInstanceState.getParcelable(STATE_CATEGORY_LIST);
             suggestionConditionMode = savedInstanceState.getInt(
                     STATE_SUGGESTION_CONDITION_MODE, suggestionConditionMode);
@@ -128,6 +134,7 @@
         mDashboardData = new DashboardData.Builder()
                 .setConditions(conditions)
                 .setSuggestions(suggestions)
+                .setSuggestionsV2(suggestionsV2)
                 .setCategory(category)
                 .setSuggestionConditionMode(suggestionConditionMode)
                 .build();
@@ -137,8 +144,12 @@
         return mDashboardData.getSuggestions();
     }
 
-    public void setCategoriesAndSuggestions(DashboardCategory category,
-            List<Tile> suggestions) {
+    /**
+     * @deprecated in favor of {@link #setCategory(DashboardCategory)} and
+     * {@link #setSuggestionsV2(List)}.
+     */
+    @Deprecated
+    public void setCategoriesAndSuggestions(DashboardCategory category, List<Tile> suggestions) {
         tintIcons(category, suggestions);
 
         final DashboardData prevData = mDashboardData;
@@ -168,6 +179,16 @@
         }
     }
 
+    public void setSuggestionsV2(List<Suggestion> data) {
+        // TODO: Tint icon
+        final DashboardData prevData = mDashboardData;
+        mDashboardData = new DashboardData.Builder(prevData)
+                .setSuggestionsV2(data)
+                .build();
+        notifyDashboardDataChanged(prevData);
+        // TODO: Replicate the metrics logging from setCategoriesAndSuggestions()
+    }
+
     public void setCategory(DashboardCategory category) {
         tintIcons(category, null);
         final DashboardData prevData = mDashboardData;
@@ -187,6 +208,10 @@
         notifyDashboardDataChanged(prevData);
     }
 
+    /**
+     * @deprecated in favor of {@link #onSuggestionDismissed(Suggestion)}.
+     */
+    @Deprecated
     public void onSuggestionDismissed(Tile suggestion) {
         final List<Tile> suggestions = mDashboardData.getSuggestions();
         if (suggestions == null || suggestions.isEmpty()) {
@@ -205,6 +230,24 @@
         }
     }
 
+    public void onSuggestionDismissed(Suggestion suggestion) {
+        final List<Suggestion> list = mDashboardData.getSuggestionsV2();
+        if (list == null || list.size() == 0) {
+            return;
+        }
+        if (list.size() == 1) {
+            // The only suggestion is dismissed, and the the empty suggestion container will
+            // remain as the dashboard item. Need to refresh the dashboard list.
+            final DashboardData prevData = mDashboardData;
+            mDashboardData = new DashboardData.Builder(prevData)
+                    .setSuggestionsV2(null)
+                    .build();
+            notifyDashboardDataChanged(prevData);
+        } else {
+            mSuggestionAdapter.removeSuggestion(suggestion);
+        }
+    }
+
     @Override
     public void notifySummaryChanged(Tile tile) {
         final int position = mDashboardData.getPositionByTile(tile);
@@ -303,10 +346,18 @@
         return mDashboardData.getItemEntityById(itemId);
     }
 
+    /**
+     * @deprecated in favor of {@link #getSuggestionV2(int)}.
+     */
+    @Deprecated
     public Tile getSuggestion(int position) {
         return mSuggestionAdapter.getSuggestion(position);
     }
 
+    public Suggestion getSuggestionV2(int position) {
+        return mSuggestionAdapter.getSuggestionsV2(position);
+    }
+
     @VisibleForTesting
     void notifyDashboardDataChanged(DashboardData prevData) {
         if (mFirstFrameDrawn && prevData != null) {
@@ -426,15 +477,30 @@
             int position) {
         // If there is suggestions to show, it will be at position 0 as we don't show the suggestion
         // header anymore.
+        final List<Suggestion> suggestionsV2 = mDashboardData.getSuggestionsV2();
         final List<Tile> suggestions = mDashboardData.getSuggestions();
-        if (position == SUGGESTION_CONDITION_HEADER_POSITION
-                && suggestions != null && suggestions.size() > 0) {
-            mSuggestionAdapter = new SuggestionAdapter(mContext, (List<Tile>)
-                    mDashboardData.getItemEntityByPosition(position), mSuggestionsShownLogged);
-            mSuggestionDismissHandler = new SuggestionDismissController(mContext,
-                    holder.data, mSuggestionParser, mCallback);
-            holder.data.setAdapter(mSuggestionAdapter);
-        } else {
+
+        boolean conditionOnly = true;
+        if (position == SUGGESTION_CONDITION_HEADER_POSITION) {
+            if (suggestions != null && suggestions.size() > 0) {
+                conditionOnly = false;
+                mSuggestionAdapter = new SuggestionAdapter(mContext, (List<Tile>)
+                        mDashboardData.getItemEntityByPosition(position),
+                        null, mSuggestionsShownLogged);
+                mSuggestionDismissHandler = new SuggestionDismissController(mContext,
+                        holder.data, mSuggestionParser, mCallback);
+                holder.data.setAdapter(mSuggestionAdapter);
+            } else if (suggestionsV2 != null && suggestionsV2.size() > 0) {
+                conditionOnly = false;
+                mSuggestionAdapter = new SuggestionAdapter(mContext, null,
+                        (List<Suggestion>) mDashboardData.getItemEntityByPosition(position),
+                        mSuggestionsShownLogged);
+                mSuggestionDismissHandler = new SuggestionDismissController(mContext,
+                        holder.data, null /* parser */, mCallback);
+                holder.data.setAdapter(mSuggestionAdapter);
+            }
+        }
+        if (conditionOnly) {
             ConditionAdapter adapter = new ConditionAdapter(mContext,
                     (List<Condition>) mDashboardData.getItemEntityByPosition(position),
                     mDashboardData.getSuggestionConditionMode());
@@ -482,11 +548,16 @@
     }
 
     void onSaveInstanceState(Bundle outState) {
-        final List<Tile> suggestions = mDashboardData.getSuggestions();
         final DashboardCategory category = mDashboardData.getCategory();
+        final List<Tile> suggestions = mDashboardData.getSuggestions();
+        final List<Suggestion> suggestionV2 = mDashboardData.getSuggestionsV2();
         if (suggestions != null) {
             outState.putParcelableArrayList(STATE_SUGGESTION_LIST, new ArrayList<>(suggestions));
         }
+        if (suggestionV2 != null) {
+            outState.putParcelableArrayList(STATE_SUGGESTION_LIST_V2,
+                    new ArrayList<>(suggestionV2));
+        }
         if (category != null) {
             outState.putParcelable(STATE_CATEGORY_LIST, category);
         }
diff --git a/src/com/android/settings/dashboard/DashboardData.java b/src/com/android/settings/dashboard/DashboardData.java
index 414757e..a14b8c2 100644
--- a/src/com/android/settings/dashboard/DashboardData.java
+++ b/src/com/android/settings/dashboard/DashboardData.java
@@ -17,6 +17,7 @@
 
 import android.annotation.IntDef;
 import android.graphics.drawable.Icon;
+import android.service.settings.suggestions.Suggestion;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.util.DiffUtil;
 import android.text.TextUtils;
@@ -69,6 +70,7 @@
     private final DashboardCategory mCategory;
     private final List<Condition> mConditions;
     private final List<Tile> mSuggestions;
+    private final List<Suggestion> mSuggestionsV2;
     @HeaderMode
     private final int mSuggestionConditionMode;
 
@@ -76,6 +78,7 @@
         mCategory = builder.mCategory;
         mConditions = builder.mConditions;
         mSuggestions = builder.mSuggestions;
+        mSuggestionsV2 = builder.mSuggestionsV2;
         mSuggestionConditionMode = builder.mSuggestionConditionMode;
 
         mItems = new ArrayList<>();
@@ -124,6 +127,10 @@
         return mSuggestions;
     }
 
+    public List<Suggestion> getSuggestionsV2() {
+        return mSuggestionsV2;
+    }
+
     public int getSuggestionConditionMode() {
         return mSuggestionConditionMode;
     }
@@ -190,13 +197,22 @@
      * and mIsShowingAll, mSuggestionConditionMode flag.
      */
     private void buildItemsData() {
-        final boolean hasSuggestions = sizeOf(mSuggestions) > 0;
+        final boolean useSuggestionV2 = mSuggestionsV2 != null;
+        final boolean hasSuggestions = useSuggestionV2
+                ? sizeOf(mSuggestionsV2) > 0
+                : sizeOf(mSuggestions) > 0;
         final List<Condition> conditions = getConditionsToShow(mConditions);
         final boolean hasConditions = sizeOf(conditions) > 0;
 
         final List<Tile> suggestions = getSuggestionsToShow(mSuggestions);
-        final int hiddenSuggestion =
-                hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0;
+        final List<Suggestion> suggestionsV2 = getSuggestionsV2ToShow(mSuggestionsV2);
+
+        final int hiddenSuggestion;
+        if (useSuggestionV2) {
+            hiddenSuggestion = hasSuggestions ? sizeOf(mSuggestionsV2) - sizeOf(suggestionsV2) : 0;
+        } else {
+            hiddenSuggestion = hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0;
+        }
 
         final boolean hasSuggestionAndCollapsed = hasSuggestions
                 && mSuggestionConditionMode == HEADER_MODE_COLLAPSED;
@@ -215,10 +231,15 @@
                 R.layout.suggestion_condition_header,
                 STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER, onlyHasConditionAndCollapsed);
 
-        /* Suggestion container. This is the card view that contains the list of suggestions.
-         * This will be added whenever the suggestion list is not empty */
-        addToItemList(suggestions, R.layout.suggestion_condition_container,
-                STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0);
+        if (useSuggestionV2) {
+            addToItemList(suggestionsV2, R.layout.suggestion_condition_container,
+                    STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestionsV2) > 0);
+        } else {
+            /* Suggestion container. This is the card view that contains the list of suggestions.
+             * This will be added whenever the suggestion list is not empty */
+            addToItemList(suggestions, R.layout.suggestion_condition_container,
+                    STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0);
+        }
 
         /* Second suggestion/condition header. This will be added when there is at least one
          * suggestion or condition that is not currently displayed, and the user can expand the
@@ -246,7 +267,7 @@
                         && !hasConditions
                         && hiddenSuggestion == 0);
 
-        if(mCategory != null) {
+        if (mCategory != null) {
             for (int j = 0; j < mCategory.tiles.size(); j++) {
                 final Tile tile = mCategory.tiles.get(j);
                 addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title),
@@ -274,6 +295,10 @@
         return result;
     }
 
+    /**
+     * @deprecated in favor of {@link #getSuggestionsV2ToShow}.
+     */
+    @Deprecated
     private List<Tile> getSuggestionsToShow(List<Tile> suggestions) {
         if (suggestions == null || mSuggestionConditionMode == HEADER_MODE_COLLAPSED) {
             return null;
@@ -285,6 +310,17 @@
         return suggestions.subList(0, DEFAULT_SUGGESTION_COUNT);
     }
 
+    private List<Suggestion> getSuggestionsV2ToShow(List<Suggestion> suggestions) {
+        if (suggestions == null || mSuggestionConditionMode == HEADER_MODE_COLLAPSED) {
+            return null;
+        }
+        if (mSuggestionConditionMode != HEADER_MODE_DEFAULT
+                || suggestions.size() <= DEFAULT_SUGGESTION_COUNT) {
+            return suggestions;
+        }
+        return suggestions.subList(0, DEFAULT_SUGGESTION_COUNT);
+    }
+
     /**
      * Builder used to build the ItemsData
      * <p>
@@ -296,7 +332,12 @@
 
         private DashboardCategory mCategory;
         private List<Condition> mConditions;
+        /**
+         * @deprecated in favor of SuggestionList
+         */
+        @Deprecated
         private List<Tile> mSuggestions;
+        private List<Suggestion> mSuggestionsV2;
 
         public Builder() {
         }
@@ -305,6 +346,7 @@
             mCategory = dashboardData.mCategory;
             mConditions = dashboardData.mConditions;
             mSuggestions = dashboardData.mSuggestions;
+            mSuggestionsV2 = dashboardData.mSuggestionsV2;
             mSuggestionConditionMode = dashboardData.mSuggestionConditionMode;
         }
 
@@ -318,11 +360,20 @@
             return this;
         }
 
+        /**
+         * @deprecated in favor of {@link #setSuggestionsV2(List)})}
+         */
+        @Deprecated
         public Builder setSuggestions(List<Tile> suggestions) {
             this.mSuggestions = suggestions;
             return this;
         }
 
+        public Builder setSuggestionsV2(List<Suggestion> suggestions) {
+            this.mSuggestionsV2 = suggestions;
+            return this;
+        }
+
         public Builder setSuggestionConditionMode(@HeaderMode int mode) {
             this.mSuggestionConditionMode = mode;
             return this;
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
index 6caf052..769a3d7 100644
--- a/src/com/android/settings/dashboard/DashboardSummary.java
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -21,6 +21,7 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
+import android.service.settings.suggestions.Suggestion;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.widget.LinearLayoutManager;
 import android.util.Log;
@@ -36,9 +37,9 @@
 import com.android.settings.dashboard.conditional.ConditionManager.ConditionListener;
 import com.android.settings.dashboard.conditional.FocusRecyclerView;
 import com.android.settings.dashboard.conditional.FocusRecyclerView.FocusListener;
+import com.android.settings.dashboard.suggestions.SuggestionControllerMixin;
 import com.android.settings.dashboard.suggestions.SuggestionDismissController;
 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
-import com.android.settings.dashboard.suggestions.SuggestionControllerMixin;
 import com.android.settings.dashboard.suggestions.SuggestionsChecks;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.ActionBarShadowController;
@@ -55,7 +56,8 @@
 
 public class DashboardSummary extends InstrumentedFragment
         implements CategoryListener, ConditionListener,
-        FocusListener, SuggestionDismissController.Callback {
+        FocusListener, SuggestionDismissController.Callback,
+        SuggestionControllerMixin.SuggestionControllerHost {
     public static final boolean DEBUG = false;
     private static final boolean DEBUG_TIMING = false;
     private static final int MAX_WAIT_MILLIS = 700;
@@ -86,7 +88,12 @@
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
-        mSuggestionControllerMixin = new SuggestionControllerMixin(context, getLifecycle());
+        mSuggestionFeatureProvider = FeatureFactory.getFactory(context)
+                .getSuggestionFeatureProvider(context);
+        if (mSuggestionFeatureProvider.isSuggestionV2Enabled(context)) {
+            mSuggestionControllerMixin = new SuggestionControllerMixin(context, this /* host */,
+                    getLifecycle());
+        }
     }
 
     @Override
@@ -96,8 +103,6 @@
         final Activity activity = getActivity();
         mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
                 .getDashboardFeatureProvider(activity);
-        mSuggestionFeatureProvider = FeatureFactory.getFactory(activity)
-                .getSuggestionFeatureProvider(activity);
 
         mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE);
 
@@ -109,8 +114,7 @@
             mSuggestionsChecks = new SuggestionsChecks(getContext());
         }
         if (DEBUG_TIMING) {
-            Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime)
-                    + " ms");
+            Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms");
         }
     }
 
@@ -221,12 +225,12 @@
     void rebuildUI() {
         if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) {
             Log.d(TAG, "Suggestion feature is disabled, skipping suggestion entirely");
-            updateCategoryAndSuggestion(null /* tiles */);
+            updateCategory();
         } else {
             new SuggestionLoader().execute();
             // Set categories on their own if loading suggestions takes too long.
             mHandler.postDelayed(() -> {
-                updateCategoryAndSuggestion(null /* tiles */);
+                updateCategory();
             }, MAX_WAIT_MILLIS);
         }
     }
@@ -264,6 +268,11 @@
     }
 
     @Override
+    public Suggestion getSuggestionAt(int position) {
+        return mAdapter.getSuggestionV2(position);
+    }
+
+    @Override
     public Tile getSuggestionForPosition(int position) {
         return mAdapter.getSuggestion(position);
     }
@@ -273,6 +282,20 @@
         mAdapter.onSuggestionDismissed(suggestion);
     }
 
+    @Override
+    public void onSuggestionDismissed(Suggestion suggestion) {
+        mAdapter.onSuggestionDismissed(suggestion);
+    }
+
+    @Override
+    public void onSuggestionReady(List<Suggestion> suggestions) {
+        mAdapter.setSuggestionsV2(suggestions);
+    }
+
+    /**
+     * @deprecated in favor of the real SuggestionLoader.
+     */
+    @Deprecated
     private class SuggestionLoader extends AsyncTask<Void, Void, List<Tile>> {
         @Override
         protected List<Tile> doInBackground(Void... params) {
@@ -311,6 +334,17 @@
         }
     }
 
+    void updateCategory() {
+        final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(
+                CategoryKey.CATEGORY_HOMEPAGE);
+        mSummaryLoader.updateSummaryToCache(category);
+        mAdapter.setCategory(category);
+    }
+
+    /**
+     * @deprecated in favor of SuggestionControllerMixin.
+     */
+    @Deprecated
     @VisibleForTesting
     void updateCategoryAndSuggestion(List<Tile> suggestions) {
         final Activity activity = getActivity();
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java
index 3815211..38b9d28 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java
@@ -15,13 +15,17 @@
  */
 package com.android.settings.dashboard.suggestions;
 
+import android.app.PendingIntent;
 import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
 import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
@@ -30,7 +34,6 @@
 import com.android.settings.dashboard.DashboardAdapter.IconCache;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.drawer.Tile;
-import com.android.settingslib.drawer.TileUtils;
 
 import java.util.List;
 import java.util.Objects;
@@ -41,14 +44,18 @@
     private final Context mContext;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private final SuggestionFeatureProvider mSuggestionFeatureProvider;
-    private List<Tile> mSuggestions;
+    @Deprecated // in favor of mNewSuggestions
+    private final List<Tile> mSuggestions;
+    private final List<Suggestion> mSuggestionsV2;
     private final IconCache mCache;
     private final List<String> mSuggestionsShownLogged;
 
     public SuggestionAdapter(Context context, List<Tile> suggestions,
+            List<Suggestion> suggestionsV2,
             List<String> suggestionsShownLogged) {
         mContext = context;
         mSuggestions = suggestions;
+        mSuggestionsV2 = suggestionsV2;
         mSuggestionsShownLogged = suggestionsShownLogged;
         mCache = new IconCache(context);
         final FeatureFactory factory = FeatureFactory.getFactory(context);
@@ -66,6 +73,68 @@
 
     @Override
     public void onBindViewHolder(DashboardItemHolder holder, int position) {
+        if (mSuggestions != null) {
+            bindSuggestionTile(holder, position);
+        } else {
+            bindSuggestion(holder, position);
+        }
+    }
+
+    private void bindSuggestion(DashboardItemHolder holder, int position) {
+        final Suggestion suggestion = mSuggestionsV2.get(position);
+        final String id = suggestion.getId();
+        if (!mSuggestionsShownLogged.contains(id)) {
+            mMetricsFeatureProvider.action(
+                    mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id);
+            mSuggestionsShownLogged.add(id);
+        }
+        // TODO: Add remote view field in Suggestion, and enable this.
+        //        if (suggestion.remoteViews != null) {
+        //            final ViewGroup itemView = (ViewGroup) holder.itemView;
+        //            itemView.removeAllViews();
+        //            itemView.addView(suggestion.remoteViews.apply(itemView.getContext(),
+        //                  itemView));
+        //        } else
+        {
+            // TODO: Add icon field in Suggestion, and enable this.
+            //            holder.icon.setImageDrawable(mCache.getIcon(suggestion.icon));
+            holder.title.setText(suggestion.getTitle());
+            final CharSequence summary = suggestion.getSummary();
+            if (!TextUtils.isEmpty(summary)) {
+                holder.summary.setText(summary);
+                holder.summary.setVisibility(View.VISIBLE);
+            } else {
+                holder.summary.setVisibility(View.GONE);
+            }
+        }
+        final View divider = holder.itemView.findViewById(R.id.divider);
+        if (divider != null) {
+            divider.setVisibility(position < mSuggestionsV2.size() - 1 ? View.VISIBLE : View.GONE);
+        }
+        View clickHandler = holder.itemView;
+        // If a view with @android:id/primary is defined, use that as the click handler
+        // instead.
+        final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
+        if (primaryAction != null) {
+            clickHandler = primaryAction;
+            // set the item view to disabled to remove any touch effects
+            holder.itemView.setEnabled(false);
+        }
+        clickHandler.setOnClickListener(v -> {
+            mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id);
+            try {
+                suggestion.getPendingIntent().send();
+            } catch (PendingIntent.CanceledException e) {
+                Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle());
+            }
+        });
+    }
+
+    /**
+     * @deprecated in favor {@link #bindSuggestion(DashboardItemHolder, int)}.
+     */
+    @Deprecated
+    private void bindSuggestionTile(DashboardItemHolder holder, int position) {
         final Tile suggestion = (Tile) mSuggestions.get(position);
         final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
                 mContext, suggestion);
@@ -77,7 +146,6 @@
             mSuggestionsShownLogged.add(suggestionId);
         }
         if (suggestion.remoteViews != null) {
-            TileUtils.updateTileUsingSummaryUri(mContext, suggestion);
             final ViewGroup itemView = (ViewGroup) holder.itemView;
             itemView.removeAllViews();
             itemView.addView(suggestion.remoteViews.apply(itemView.getContext(), itemView));
@@ -115,24 +183,46 @@
 
     @Override
     public long getItemId(int position) {
-        return Objects.hash(mSuggestions.get(position).title);
+        if (mSuggestions != null) {
+            return Objects.hash(mSuggestions.get(position).title);
+        } else {
+            return Objects.hash(mSuggestionsV2.get(position).getId());
+        }
     }
 
     @Override
     public int getItemViewType(int position) {
-        Tile suggestion = getSuggestion(position);
-        return suggestion.remoteViews != null
-                ? R.layout.suggestion_tile_remote_container
-                : R.layout.suggestion_tile;
+        if (mSuggestions != null) {
+            Tile suggestion = getSuggestion(position);
+
+            return suggestion.remoteViews != null
+                    ? R.layout.suggestion_tile_remote_container
+                    : R.layout.suggestion_tile;
+        } else {
+
+            return R.layout.suggestion_tile;
+            // TODO: Add remote view field in Suggestion, and enable this.
+            //            Suggestion suggestion = getSuggestionsV2(position);
+            //            return suggestion.remoteViews != null
+            //                    ? R.layout.suggestion_tile_remote_container
+            //                    : R.layout.suggestion_tile;
+        }
     }
 
     @Override
     public int getItemCount() {
-        return mSuggestions.size();
+        if (mSuggestions != null) {
+            return mSuggestions.size();
+        } else {
+            return mSuggestionsV2.size();
+        }
     }
 
     public Tile getSuggestion(int position) {
         final long itemId = getItemId(position);
+        if (mSuggestions == null) {
+            return null;
+        }
         for (Tile tile : mSuggestions) {
             if (Objects.hash(tile.title) == itemId) {
                 return tile;
@@ -141,6 +231,19 @@
         return null;
     }
 
+    public Suggestion getSuggestionsV2(int position) {
+        final long itemId = getItemId(position);
+        if (mSuggestionsV2 == null) {
+            return null;
+        }
+        for (Suggestion suggestion : mSuggestionsV2) {
+            if (Objects.hash(suggestion.getId()) == itemId) {
+                return suggestion;
+            }
+        }
+        return null;
+    }
+
     public void removeSuggestion(Tile suggestion) {
         mSuggestions.remove(suggestion);
         notifyDataSetChanged();
@@ -151,4 +254,8 @@
                 mSuggestionFeatureProvider.isSmartSuggestionEnabled(mContext));
     }
 
+    public void removeSuggestion(Suggestion suggestion) {
+        mSuggestionsV2.remove(suggestion);
+        notifyDataSetChanged();
+    }
 }
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java b/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java
index 6f5c82e..21ae435 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java
@@ -16,11 +16,12 @@
 
 package com.android.settings.dashboard.suggestions;
 
+import android.app.LoaderManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Loader;
+import android.os.Bundle;
 import android.service.settings.suggestions.Suggestion;
-import android.support.annotation.VisibleForTesting;
-import android.util.FeatureFlagUtils;
 import android.util.Log;
 
 import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -34,23 +35,32 @@
  * Manages IPC communication to SettingsIntelligence for suggestion related services.
  */
 public class SuggestionControllerMixin implements SuggestionController.ServiceConnectionListener,
-        LifecycleObserver, OnStart, OnStop {
+        LifecycleObserver, OnStart, OnStop, LoaderManager.LoaderCallbacks<List<Suggestion>> {
 
-    @VisibleForTesting
-    static final String FEATURE_FLAG = "new_settings_suggestion";
+    public interface SuggestionControllerHost {
+        /**
+         * Called when suggestion data fetching is ready.
+         */
+        void onSuggestionReady(List<Suggestion> data);
+
+        /**
+         * Returns {@link LoaderManager} associated with the host.
+         */
+        LoaderManager getLoaderManager();
+    }
+
     private static final String TAG = "SuggestionCtrlMixin";
     private static final boolean DEBUG = false;
 
     private final Context mContext;
     private final SuggestionController mSuggestionController;
+    private final SuggestionControllerHost mHost;
 
-    public static boolean isEnabled() {
-        return FeatureFlagUtils.isEnabled(FEATURE_FLAG);
-    }
-
-    public SuggestionControllerMixin(Context context, Lifecycle lifecycle) {
+    public SuggestionControllerMixin(Context context, SuggestionControllerHost host,
+            Lifecycle lifecycle) {
         mContext = context.getApplicationContext();
-        mSuggestionController = new SuggestionController(context,
+        mHost = host;
+        mSuggestionController = new SuggestionController(mContext,
                 new ComponentName(
                         "com.android.settings.intelligence",
                         "com.android.settings.intelligence.suggestions.SuggestionService"),
@@ -62,10 +72,6 @@
 
     @Override
     public void onStart() {
-        if (!isEnabled()) {
-            Log.w(TAG, "Feature not enabled, skipping");
-            return;
-        }
         mSuggestionController.start();
     }
 
@@ -76,11 +82,8 @@
 
     @Override
     public void onServiceConnected() {
-        // TODO: Call API to get data from a loader instead of in current thread.
-        final List<Suggestion> data = mSuggestionController.getSuggestions();
-        if (DEBUG) {
-            Log.d(TAG, "data size " + (data == null ? 0 : data.size()));
-        }
+        mHost.getLoaderManager().restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
+                null /* args */, this /* callback */);
     }
 
     @Override
@@ -88,5 +91,24 @@
         if (DEBUG) {
             Log.d(TAG, "SuggestionService disconnected");
         }
+        mHost.getLoaderManager().destroyLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS);
+    }
+
+    @Override
+    public Loader<List<Suggestion>> onCreateLoader(int id, Bundle args) {
+        if (id == SuggestionLoader.LOADER_ID_SUGGESTIONS) {
+            return new SuggestionLoader(mContext, mSuggestionController);
+        }
+        throw new IllegalArgumentException("This loader id is not supported " + id);
+    }
+
+    @Override
+    public void onLoadFinished(Loader<List<Suggestion>> loader, List<Suggestion> data) {
+        mHost.onSuggestionReady(data);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<List<Suggestion>> loader) {
+
     }
 }
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
index 100c955..6affa8c 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
@@ -17,6 +17,7 @@
 package com.android.settings.dashboard.suggestions;
 
 import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.helper.ItemTouchHelper;
 
@@ -30,18 +31,37 @@
     public interface Callback {
 
         /**
+         * @deprecated in favor of {@link #getSuggestionAt(int)}
          * Returns suggestion tile data from the callback
          */
+        @Deprecated
         Tile getSuggestionForPosition(int position);
 
         /**
+         * @deprecated in favor of {@link #onSuggestionDismissed(Suggestion)}
+         * Called when a suggestion is dismissed.
+         */
+        @Deprecated
+        void onSuggestionDismissed(Tile suggestion);
+
+        /**
+         * Returns suggestion tile data from the callback
+         */
+        Suggestion getSuggestionAt(int position);
+
+        /**
          * Called when a suggestion is dismissed.
          */
-        void onSuggestionDismissed(Tile suggestion);
+        void onSuggestionDismissed(Suggestion suggestion);
     }
 
     private final Context mContext;
     private final SuggestionFeatureProvider mSuggestionFeatureProvider;
+
+    /**
+     * @deprecated in favor of the new Suggestion backend.
+     */
+    @Deprecated
     private final SuggestionParser mSuggestionParser;
     private final Callback mCallback;
 
@@ -79,8 +99,15 @@
         if (mCallback == null) {
             return;
         }
-        final Tile suggestion = mCallback.getSuggestionForPosition(viewHolder.getAdapterPosition());
-        mSuggestionFeatureProvider.dismissSuggestion(mContext, mSuggestionParser, suggestion);
-        mCallback.onSuggestionDismissed(suggestion);
+        final int position = viewHolder.getAdapterPosition();
+        final Suggestion suggestionV2 = mCallback.getSuggestionAt(position);
+        if (suggestionV2 != null) {
+            mSuggestionFeatureProvider.dismissSuggestion(mContext, suggestionV2);
+            mCallback.onSuggestionDismissed(suggestionV2);
+        } else {
+            final Tile suggestion = mCallback.getSuggestionForPosition(position);
+            mSuggestionFeatureProvider.dismissSuggestion(mContext, mSuggestionParser, suggestion);
+            mCallback.onSuggestionDismissed(suggestion);
+        }
     }
 }
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java
index d19a778..b649572 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.service.settings.suggestions.Suggestion;
 import android.support.annotation.NonNull;
 
 import com.android.settingslib.drawer.Tile;
@@ -31,10 +32,17 @@
 
     /**
      * Whether or not the whole suggestion feature is enabled.
+     * @deprecated in favor of {@link #isSuggestionV2Enabled(Context)}
      */
+    @Deprecated
     boolean isSuggestionEnabled(Context context);
 
     /**
+     * Whether or not the suggestion v2 feature is enabled.
+     */
+    boolean isSuggestionV2Enabled(Context context);
+
+    /**
      * Returns true if smart suggestion should be used instead of xml based SuggestionParser.
      */
     boolean isSmartSuggestionEnabled(Context context);
@@ -52,7 +60,9 @@
      *
      * @param suggestions   List of suggestion Tiles
      * @param suggestionIds List of suggestion ids corresponding to the suggestion tiles.
+     * @deprecated in favor of SettingsIntelligence
      */
+    @Deprecated
     void rankSuggestions(final List<Tile> suggestions, List<String> suggestionIds);
 
     /**
@@ -62,10 +72,18 @@
 
     /**
      * Dismisses a suggestion.
+     *
+     * @deprecated in favor of {@link #dismissSuggestion(Context, Suggestion)}
      */
+    @Deprecated
     void dismissSuggestion(Context context, SuggestionParser parser, Tile suggestion);
 
     /**
+     * Dismisses a suggestion.
+     */
+    void dismissSuggestion(Context context, Suggestion suggestion);
+
+    /**
      * Returns an identifier for the suggestion
      */
     String getSuggestionIdentifier(Context context, Tile suggestion);
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java
index 3d40d96..1c61d8e 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java
@@ -23,11 +23,14 @@
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.provider.Settings.Secure;
+import android.service.settings.suggestions.Suggestion;
 import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.Settings.AmbientDisplayPickupSuggestionActivity;
 import com.android.settings.Settings.AmbientDisplaySuggestionActivity;
@@ -46,7 +49,6 @@
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.suggestions.SuggestionParser;
 
-import java.util.ArrayList;
 import java.util.List;
 
 public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider {
@@ -55,6 +57,8 @@
     private static final int EXCLUSIVE_SUGGESTION_MAX_COUNT = 3;
 
     private static final String SHARED_PREF_FILENAME = "suggestions";
+    @VisibleForTesting
+    static final String FEATURE_FLAG_SUGGESTIONS_V2 = "new_settings_suggestion";
 
     private final SuggestionRanker mSuggestionRanker;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
@@ -63,7 +67,20 @@
     public boolean isSuggestionEnabled(Context context) {
         final ActivityManager am =
                 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-        return !am.isLowRamDevice();
+        boolean isLowRamDevice = am.isLowRamDevice();
+        return !isLowRamDevice && !isV2Enabled();
+    }
+
+    @Override
+    public boolean isSuggestionV2Enabled(Context context) {
+        final ActivityManager am =
+                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        boolean isLowRamDevice = am.isLowRamDevice();
+        return !isLowRamDevice && isV2Enabled();
+    }
+
+    private static boolean isV2Enabled() {
+        return FeatureFlagUtils.isEnabled(FEATURE_FLAG_SUGGESTIONS_V2);
     }
 
     @Override
@@ -149,6 +166,17 @@
     }
 
     @Override
+    public void dismissSuggestion(Context context, Suggestion suggestion) {
+        if (suggestion == null || context == null) {
+            return;
+        }
+        mMetricsFeatureProvider.action(
+                context, MetricsProto.MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION,
+                suggestion.getId());
+        // TODO: Call SettingsIntelligence to dismiss suggestion.
+    }
+
+    @Override
     public String getSuggestionIdentifier(Context context, Tile suggestion) {
         if (suggestion.intent == null || suggestion.intent.getComponent() == null
                 || context == null) {
@@ -166,8 +194,7 @@
     @VisibleForTesting
     boolean hasUsedNightDisplay(Context context) {
         final ContentResolver cr = context.getContentResolver();
-        final long lastActivatedTimeMillis = Secure.getLong(cr,
-                Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1);
-        return lastActivatedTimeMillis > 0;
+        return Secure.getInt(cr, Secure.NIGHT_DISPLAY_AUTO_MODE, 0) != 0
+                || Secure.getString(cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME) != null;
     }
 }
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java b/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java
new file mode 100644
index 0000000..b9d51ce
--- /dev/null
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.dashboard.suggestions;
+
+import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
+import android.util.Log;
+
+import com.android.settings.utils.AsyncLoader;
+
+import java.util.List;
+
+public class SuggestionLoader extends AsyncLoader<List<Suggestion>> {
+
+    public static final int LOADER_ID_SUGGESTIONS = 42;
+    private static final String TAG = "SuggestionLoader";
+
+    private final SuggestionController mSuggestionController;
+
+    public SuggestionLoader(Context context, SuggestionController controller) {
+        super(context);
+        mSuggestionController = controller;
+    }
+
+    @Override
+    protected void onDiscardResult(List<Suggestion> result) {
+
+    }
+
+    @Override
+    public List<Suggestion> loadInBackground() {
+        final List<Suggestion> data = mSuggestionController.getSuggestions();
+        if (data == null) {
+            Log.d(TAG, "data is null");
+        } else {
+            Log.d(TAG, "data size " + data.size());
+        }
+        return data;
+    }
+}
diff --git a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
index 7734e93..44a2ecd 100644
--- a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
@@ -69,12 +69,12 @@
     }
 
     @Override
-    public void onDeveloperOptionsEnabled() {
+    protected void onDeveloperOptionsSwitchEnabled() {
         mPreference.setEnabled(true);
     }
 
     @Override
-    public void onDeveloperOptionsDisabled() {
+    protected void onDeveloperOptionsSwitchDisabled() {
         SystemProperties.set(BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, Boolean.toString(false));
         mPreference.setChecked(false);
         mPreference.setEnabled(false);
diff --git a/src/com/android/settings/development/ColorModePreference.java b/src/com/android/settings/development/ColorModePreference.java
index e0b0837..20af2c6 100644
--- a/src/com/android/settings/development/ColorModePreference.java
+++ b/src/com/android/settings/development/ColorModePreference.java
@@ -28,6 +28,7 @@
 import com.android.settings.R;
 
 import java.util.ArrayList;
+import java.util.List;
 
 public class ColorModePreference extends SwitchPreference implements DisplayListener {
 
@@ -35,7 +36,28 @@
     private Display mDisplay;
 
     private int mCurrentIndex;
-    private ArrayList<ColorModeDescription> mDescriptions;
+    private List<ColorModeDescription> mDescriptions;
+
+    public static List<ColorModeDescription> getColorModeDescriptions(Context context) {
+
+        List<ColorModeDescription> colorModeDescriptions = new ArrayList<>();
+        Resources resources = context.getResources();
+        int[] colorModes = resources.getIntArray(R.array.color_mode_ids);
+        String[] titles = resources.getStringArray(R.array.color_mode_names);
+        String[] descriptions = resources.getStringArray(R.array.color_mode_descriptions);
+        // Map the resource information describing color modes.
+        for (int i = 0; i < colorModes.length; i++) {
+            if (colorModes[i] != -1 && i != 1 /* Skip Natural for now. */) {
+                ColorModeDescription desc = new ColorModeDescription();
+                desc.colorMode = colorModes[i];
+                desc.title = titles[i];
+                desc.summary = descriptions[i];
+                colorModeDescriptions.add(desc);
+            }
+        }
+
+        return colorModeDescriptions;
+    }
 
     public ColorModePreference(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -75,22 +97,7 @@
     public void updateCurrentAndSupported() {
         mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
 
-        mDescriptions = new ArrayList<>();
-
-        Resources resources = getContext().getResources();
-        int[] colorModes = resources.getIntArray(R.array.color_mode_ids);
-        String[] titles = resources.getStringArray(R.array.color_mode_names);
-        String[] descriptions = resources.getStringArray(R.array.color_mode_descriptions);
-        // Map the resource information describing color modes.
-        for (int i = 0; i < colorModes.length; i++) {
-            if (colorModes[i] != -1 && i != 1 /* Skip Natural for now. */) {
-                ColorModeDescription desc = new ColorModeDescription();
-                desc.colorMode = colorModes[i];
-                desc.title = titles[i];
-                desc.summary = descriptions[i];
-                mDescriptions.add(desc);
-            }
-        }
+        mDescriptions = getColorModeDescriptions(getContext());
 
         int currentColorMode = mDisplay.getColorMode();
         mCurrentIndex = -1;
diff --git a/src/com/android/settings/development/DeveloperOptionsPreferenceController.java b/src/com/android/settings/development/DeveloperOptionsPreferenceController.java
index a7d45eb..2f1f254 100644
--- a/src/com/android/settings/development/DeveloperOptionsPreferenceController.java
+++ b/src/com/android/settings/development/DeveloperOptionsPreferenceController.java
@@ -17,6 +17,7 @@
 package com.android.settings.development;
 
 import android.content.Context;
+import android.content.Intent;
 
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.core.AbstractPreferenceController;
@@ -35,12 +36,47 @@
     }
 
     /**
-     * Called when developer options is enabled
+     * Called when an activity returns to the DeveloperSettingsDashboardFragment.
+     *
+     * @param requestCode The integer request code originally supplied to
+     *                    startActivityForResult(), allowing you to identify who this
+     *                    result came from.
+     * @param resultCode  The integer result code returned by the child activity
+     *                    through its setResult().
+     * @param data        An Intent, which can return result data to the caller
+     *                    (various data can be attached to Intent "extras").
+     * @return true if the controller handled the activity result
      */
-    public abstract void onDeveloperOptionsEnabled();
+    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+        return false;
+    }
 
     /**
-     *Called when developer options is disabled
+     * Called when developer options is enabled
      */
-    public abstract void onDeveloperOptionsDisabled();
+    public void onDeveloperOptionsEnabled() {
+        if (isAvailable()) {
+            onDeveloperOptionsSwitchEnabled();
+        }
+    }
+
+    /**
+     * Called when developer options is disabled
+     */
+    public void onDeveloperOptionsDisabled() {
+        if (isAvailable()) {
+            onDeveloperOptionsSwitchDisabled();
+        }
+    }
+
+    /**
+     * Called when developer options is enabled and the preference is available
+     */
+    protected abstract void onDeveloperOptionsSwitchEnabled();
+
+    /**
+     * Called when developer options is disabled and the preference is available
+     */
+    protected abstract void onDeveloperOptionsSwitchDisabled();
+
 }
diff --git a/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java b/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java
new file mode 100644
index 0000000..54d1fa3
--- /dev/null
+++ b/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java
@@ -0,0 +1,24 @@
+/*
+ * 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.development;
+
+/**
+ * Interface for storing Activity request codes in development options
+ */
+public interface DevelopmentOptionsActivityRequestCodes {
+    int REQUEST_CODE_ENABLE_OEM_UNLOCK = 0;
+}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 8df25c2..d334bd0 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -16,10 +16,13 @@
 
 package com.android.settings.development;
 
+import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.UserManager;
 import android.provider.SearchIndexableResource;
+import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 import android.widget.Switch;
 
@@ -40,7 +43,7 @@
 import java.util.List;
 
 public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragment
-        implements SwitchBar.OnSwitchChangeListener {
+        implements SwitchBar.OnSwitchChangeListener, OemUnlockDialogHost {
 
     private static final String TAG = "DevSettingsDashboard";
 
@@ -104,6 +107,33 @@
     }
 
     @Override
+    public void onOemUnlockDialogConfirmed() {
+        final OemUnlockPreferenceController controller = getDevelopmentOptionsController(
+                OemUnlockPreferenceController.class);
+        controller.onOemUnlockConfirmed();
+    }
+
+    @Override
+    public void onOemUnlockDialogDismissed() {
+        final OemUnlockPreferenceController controller = getDevelopmentOptionsController(
+                OemUnlockPreferenceController.class);
+        controller.onOemUnlockDismissed();
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        for (AbstractPreferenceController controller : mPreferenceControllers) {
+            if (controller instanceof DeveloperOptionsPreferenceController) {
+                if (((DeveloperOptionsPreferenceController) controller).onActivityResult(
+                        requestCode, resultCode, data)) {
+                    return;
+                }
+            }
+        }
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    @Override
     protected String getLogTag() {
         return TAG;
     }
@@ -121,7 +151,8 @@
 
     @Override
     protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
-        mPreferenceControllers = buildPreferenceControllers(context, getLifecycle());
+        mPreferenceControllers = buildPreferenceControllers(context, getActivity(), getLifecycle(),
+                this /* devOptionsDashboardFragment */);
         return mPreferenceControllers;
     }
 
@@ -140,14 +171,92 @@
     }
 
     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
-            Lifecycle lifecycle) {
+            Activity activity, Lifecycle lifecycle, DevelopmentSettingsDashboardFragment fragment) {
         final List<AbstractPreferenceController> controllers = new ArrayList<>();
+        // take bug report
+        // desktop backup password
         controllers.add(new StayAwakePreferenceController(context, lifecycle));
+        // hdcp checking
         controllers.add(new BluetoothSnoopLogPreferenceController(context));
-
+        controllers.add(new OemUnlockPreferenceController(context, activity, fragment));
+        // running services
+        // convert to file encryption
+        controllers.add(new PictureColorModePreferenceController(context, lifecycle));
+        // webview implementation
+        // cool color temperature
+        // automatic system updates
+        // system ui demo mode
+        // quick settings developer tiles
+        // usb debugging
+        // revoke usb debugging authorizations
+        // local terminal
+        // bug report shortcut
+        // select mock location app
+        // enable view attribute inspection
+        // select debug app
+        // wait for debugger
+        // verify apps over usb
+        // logger buffer sizes
+        // store logger data persistently on device
+        // telephony monitor
+        // camera laser sensor
+        // camera HAL HDR+
+        // feature flags
+        // wireless display certification
+        // enable wi-fi verbose logging
+        // aggressive wifi to mobile handover
+        // always allow wifi roam scans
+        // mobile always active
+        // tethering hardware acceleration
+        // select usb configuration
+        // show bluetooth devices without names
+        // disable absolute volume
+        // enable in-band ringing
+        // bluetooth avrcp version
+        // bluetooth audio codec
+        // bluetooth audio sample rate
+        // bluetooth audio bits per sample
+        // bluetooth audio channel mode
+        // bluetooth audio ldac codec: playback quality
+        // show taps
+        // pointer location
+        // show surface updates
+        // show layout bounds
+        // force rtl layout direction
+        // window animation scale
+        // transition animation scale
+        // animator duration scale
+        // simulate secondary displays
+        // smallest width
+        // force gpu rendering
+        // show gpu view updates
+        // show hardware layers updates
+        // debug gpu overdraw
+        // debug non-rectangular clip operations
+        // force 4x msaa
+        // disable hw overlays
+        // simulate color space
+        // set gpu renderer
+        // disable usb audio routing
+        // strict mode enabled
+        // profile gpu rendering
+        // don't keep activities
+        // background process limit
+        // background check
+        // show all anrs
+        // show notification channel warnings
+        // inactive apps
+        // force allow apps on external
+        // force activities to be resizable
+        // reset shortcutmanager rate-limiting
         return controllers;
     }
 
+    @VisibleForTesting
+    <T extends AbstractPreferenceController> T getDevelopmentOptionsController(Class<T> clazz) {
+        return getPreferenceController(clazz);
+    }
+
     /**
      * For Search.
      */
@@ -171,7 +280,8 @@
                 @Override
                 public List<AbstractPreferenceController> getPreferenceControllers(Context
                         context) {
-                    return buildPreferenceControllers(context, null /* lifecycle */);
+                    return buildPreferenceControllers(context, null /* activity */,
+                            null /* lifecycle */, null /* devOptionsDashboardFragment */);
                 }
             };
 }
diff --git a/src/com/android/settings/development/EnableOemUnlockSettingWarningDialog.java b/src/com/android/settings/development/EnableOemUnlockSettingWarningDialog.java
new file mode 100644
index 0000000..2486ef5
--- /dev/null
+++ b/src/com/android/settings/development/EnableOemUnlockSettingWarningDialog.java
@@ -0,0 +1,82 @@
+/*
+ * 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.development;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+public class EnableOemUnlockSettingWarningDialog extends InstrumentedDialogFragment implements
+        DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+
+    public static final String TAG = "EnableOemUnlockDlg";
+
+    public static void show(Fragment host) {
+        final FragmentManager manager = host.getActivity().getFragmentManager();
+        if (manager.findFragmentByTag(TAG) == null) {
+            final EnableOemUnlockSettingWarningDialog dialog =
+                    new EnableOemUnlockSettingWarningDialog();
+            dialog.setTargetFragment(host, 0 /* requestCode */);
+            dialog.show(manager, TAG);
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DIALOG_ENABLE_OEM_UNLOCKING;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return new AlertDialog.Builder(getActivity())
+                .setTitle(R.string.confirm_enable_oem_unlock_title)
+                .setMessage(R.string.confirm_enable_oem_unlock_text)
+                .setPositiveButton(R.string.enable_text, this /* onClickListener */)
+                .setNegativeButton(android.R.string.cancel, this /* onClickListener */)
+                .create();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        final OemUnlockDialogHost host = (OemUnlockDialogHost) getTargetFragment();
+        if (host == null) {
+            return;
+        }
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            host.onOemUnlockDialogConfirmed();
+        } else {
+            host.onOemUnlockDialogDismissed();
+        }
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        super.onDismiss(dialog);
+        final OemUnlockDialogHost host = (OemUnlockDialogHost) getTargetFragment();
+        if (host == null) {
+            return;
+        }
+        host.onOemUnlockDialogDismissed();
+    }
+}
diff --git a/src/com/android/settings/development/OemUnlockDialogHost.java b/src/com/android/settings/development/OemUnlockDialogHost.java
new file mode 100644
index 0000000..c134e9c
--- /dev/null
+++ b/src/com/android/settings/development/OemUnlockDialogHost.java
@@ -0,0 +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.development;
+
+/**
+ * Interface for OemUnlockDialogFragment callbacks.
+ */
+public interface OemUnlockDialogHost {
+
+    /**
+     * Called when the user presses enable on the warning dialog.
+     */
+    void onOemUnlockDialogConfirmed();
+
+    /**
+     * Called when the user dismisses or cancels the warning dialog.
+     */
+    void onOemUnlockDialogDismissed();
+}
diff --git a/src/com/android/settings/development/OemUnlockPreferenceController.java b/src/com/android/settings/development/OemUnlockPreferenceController.java
new file mode 100644
index 0000000..cb391a8
--- /dev/null
+++ b/src/com/android/settings/development/OemUnlockPreferenceController.java
@@ -0,0 +1,216 @@
+/*
+ * 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.development;
+
+import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes
+        .REQUEST_CODE_ENABLE_OEM_UNLOCK;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.oemlock.OemLockManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.telephony.TelephonyManager;
+
+import com.android.settings.R;
+import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settingslib.RestrictedSwitchPreference;
+
+public class OemUnlockPreferenceController extends DeveloperOptionsPreferenceController implements
+        Preference.OnPreferenceChangeListener {
+
+    private static final String PREFERENCE_KEY = "oem_unlock_enable";
+
+    private final OemLockManager mOemLockManager;
+    private final UserManager mUserManager;
+    private final TelephonyManager mTelephonyManager;
+    private final DevelopmentSettingsDashboardFragment mFragment;
+    private final ChooseLockSettingsHelper mChooseLockSettingsHelper;
+    private RestrictedSwitchPreference mPreference;
+
+    public OemUnlockPreferenceController(Context context, Activity activity,
+            DevelopmentSettingsDashboardFragment fragment) {
+        super(context);
+        mOemLockManager = (OemLockManager) context.getSystemService(Context.OEM_LOCK_SERVICE);
+        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        mFragment = fragment;
+        if (activity != null || mFragment != null) {
+            mChooseLockSettingsHelper = new ChooseLockSettingsHelper(activity, mFragment);
+        } else {
+            mChooseLockSettingsHelper = null;
+        }
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mOemLockManager != null;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return PREFERENCE_KEY;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+
+        mPreference = (RestrictedSwitchPreference) screen.findPreference(getPreferenceKey());
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        boolean isUnlocked = (Boolean) newValue;
+        if (isUnlocked) {
+            if (!showKeyguardConfirmation(mContext.getResources(),
+                    REQUEST_CODE_ENABLE_OEM_UNLOCK)) {
+                confirmEnableOemUnlock();
+            }
+        } else {
+            mOemLockManager.setOemUnlockAllowedByUser(false);
+        }
+        return true;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+        mPreference.setChecked(mOemLockManager.isOemUnlockAllowed());
+        updateOemUnlockSettingDescription();
+        // Showing mEnableOemUnlock preference as device has persistent data block.
+        mPreference.setDisabledByAdmin(null);
+        mPreference.setEnabled(enableOemUnlockPreference());
+        if (mPreference.isEnabled()) {
+            // Check restriction, disable mEnableOemUnlock and apply policy transparency.
+            mPreference.checkRestrictionAndSetDisabled(UserManager.DISALLOW_FACTORY_RESET);
+        }
+    }
+
+    @Override
+    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_CODE_ENABLE_OEM_UNLOCK) {
+            if (resultCode == Activity.RESULT_OK) {
+                if (mPreference.isChecked()) {
+                    confirmEnableOemUnlock();
+                } else {
+                    mOemLockManager.setOemUnlockAllowedByUser(false);
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void onDeveloperOptionsSwitchEnabled() {
+        handleDeveloperOptionsToggled();
+    }
+
+    @Override
+    protected void onDeveloperOptionsSwitchDisabled() {
+        handleDeveloperOptionsToggled();
+    }
+
+    public void onOemUnlockConfirmed() {
+        mOemLockManager.setOemUnlockAllowedByUser(true);
+    }
+
+    public void onOemUnlockDismissed() {
+        if (mPreference == null) {
+            return;
+        }
+        updateState(mPreference);
+    }
+
+    private void handleDeveloperOptionsToggled() {
+        mPreference.setEnabled(enableOemUnlockPreference());
+        if (mPreference.isEnabled()) {
+            // Check restriction, disable mEnableOemUnlock and apply policy transparency.
+            mPreference.checkRestrictionAndSetDisabled(UserManager.DISALLOW_FACTORY_RESET);
+        }
+    }
+
+    private void updateOemUnlockSettingDescription() {
+        int oemUnlockSummary = R.string.oem_unlock_enable_summary;
+        if (isBootloaderUnlocked()) {
+            oemUnlockSummary = R.string.oem_unlock_enable_disabled_summary_bootloader_unlocked;
+        } else if (isSimLockedDevice()) {
+            oemUnlockSummary = R.string.oem_unlock_enable_disabled_summary_sim_locked_device;
+        } else if (!isOemUnlockAllowedByUserAndCarrier()) {
+            // If the device isn't SIM-locked but OEM unlock is disallowed by some party, this
+            // means either some other carrier restriction is in place or the device hasn't been
+            // able to confirm which restrictions (SIM-lock or otherwise) apply.
+            oemUnlockSummary =
+                    R.string.oem_unlock_enable_disabled_summary_connectivity_or_locked;
+        }
+        mPreference.setSummary(mContext.getResources().getString(oemUnlockSummary));
+    }
+
+    /** Returns {@code true} if the device is SIM-locked. Otherwise, returns {@code false}. */
+    private boolean isSimLockedDevice() {
+        int phoneCount = mTelephonyManager.getPhoneCount();
+        for (int i = 0; i < phoneCount; i++) {
+            if (mTelephonyManager.getAllowedCarriers(i).size() > 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns {@code true} if the bootloader has been unlocked. Otherwise, returns {code false}.
+     */
+    private boolean isBootloaderUnlocked() {
+        return mOemLockManager.isDeviceOemUnlocked();
+    }
+
+    private boolean enableOemUnlockPreference() {
+        return !isBootloaderUnlocked() && isOemUnlockAllowedByUserAndCarrier();
+    }
+
+
+    @VisibleForTesting
+    boolean showKeyguardConfirmation(Resources resources, int requestCode) {
+        return mChooseLockSettingsHelper.launchConfirmationActivity(
+                requestCode, resources.getString(R.string.oem_unlock_enable));
+    }
+
+    @VisibleForTesting
+    void confirmEnableOemUnlock() {
+        EnableOemUnlockSettingWarningDialog.show(mFragment);
+    }
+
+    /**
+     * Returns whether OEM unlock is allowed by the user and carrier.
+     *
+     * This does not take into account any restrictions imposed by the device policy.
+     */
+    @VisibleForTesting
+    boolean isOemUnlockAllowedByUserAndCarrier() {
+        final UserHandle userHandle = UserHandle.of(UserHandle.myUserId());
+        return mOemLockManager.isOemUnlockAllowedByCarrier()
+                && !mUserManager.hasBaseUserRestriction(UserManager.DISALLOW_FACTORY_RESET,
+                userHandle);
+    }
+
+}
diff --git a/src/com/android/settings/development/PictureColorModePreferenceController.java b/src/com/android/settings/development/PictureColorModePreferenceController.java
new file mode 100644
index 0000000..fe4755f
--- /dev/null
+++ b/src/com/android/settings/development/PictureColorModePreferenceController.java
@@ -0,0 +1,99 @@
+/*
+ * 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.development;
+
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+public class PictureColorModePreferenceController extends
+        DeveloperOptionsPreferenceController implements
+        LifecycleObserver, OnResume, OnPause {
+
+    private static final String KEY_COLOR_MODE = "picture_color_mode";
+
+    private ColorModePreference mPreference;
+
+    public PictureColorModePreferenceController(Context context, Lifecycle lifecycle) {
+        super(context);
+
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return getColorModeDescriptionsSize() > 1 && !isWideColorGamut();
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_COLOR_MODE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mPreference = (ColorModePreference) screen.findPreference(getPreferenceKey());
+        if (mPreference != null) {
+            mPreference.updateCurrentAndSupported();
+        }
+    }
+
+    @Override
+    public void onResume() {
+        if (mPreference == null) {
+            return;
+        }
+        mPreference.startListening();
+        mPreference.updateCurrentAndSupported();
+    }
+
+    @Override
+    public void onPause() {
+        if (mPreference == null) {
+            return;
+        }
+        mPreference.stopListening();
+    }
+    
+    @Override
+    protected void onDeveloperOptionsSwitchEnabled() {
+        mPreference.setEnabled(true);
+    }
+
+    @Override
+    protected void onDeveloperOptionsSwitchDisabled() {
+        mPreference.setEnabled(false);
+    }
+
+    @VisibleForTesting
+    boolean isWideColorGamut() {
+        return mContext.getDisplay().isWideColorGamut();
+    }
+
+    @VisibleForTesting
+    int getColorModeDescriptionsSize() {
+        return ColorModePreference.getColorModeDescriptions(mContext).size();
+    }
+}
diff --git a/src/com/android/settings/development/StayAwakePreferenceController.java b/src/com/android/settings/development/StayAwakePreferenceController.java
index a590d7d..ecbb9d0 100644
--- a/src/com/android/settings/development/StayAwakePreferenceController.java
+++ b/src/com/android/settings/development/StayAwakePreferenceController.java
@@ -101,19 +101,6 @@
     }
 
     @Override
-    public void onDeveloperOptionsEnabled() {
-        mPreference.setEnabled(true);
-    }
-
-    @Override
-    public void onDeveloperOptionsDisabled() {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, SETTING_VALUE_OFF);
-        mPreference.setChecked(false);
-        mPreference.setEnabled(false);
-    }
-
-    @Override
     public void onResume() {
         if (mPreference != null) {
             mSettingsObserver.register(true /* register */);
@@ -127,6 +114,19 @@
         }
     }
 
+    @Override
+    protected void onDeveloperOptionsSwitchEnabled() {
+        mPreference.setEnabled(true);
+    }
+
+    @Override
+    protected void onDeveloperOptionsSwitchDisabled() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, SETTING_VALUE_OFF);
+        mPreference.setChecked(false);
+        mPreference.setEnabled(false);
+    }
+
     @VisibleForTesting
     RestrictedLockUtils.EnforcedAdmin checkIfMaximumTimeToLockSetByAdmin() {
         // A DeviceAdmin has specified a maximum time until the device
diff --git a/src/com/android/settings/display/NightDisplayPreference.java b/src/com/android/settings/display/NightDisplayPreference.java
index 38b57a2..b966530 100644
--- a/src/com/android/settings/display/NightDisplayPreference.java
+++ b/src/com/android/settings/display/NightDisplayPreference.java
@@ -22,6 +22,7 @@
 import com.android.settings.R;
 
 import java.text.DateFormat;
+import java.time.LocalTime;
 import java.util.Calendar;
 import java.util.TimeZone;
 
@@ -58,11 +59,11 @@
         mController.setListener(null);
     }
 
-    private String getFormattedTimeString(NightDisplayController.LocalTime localTime) {
+    private String getFormattedTimeString(LocalTime localTime) {
         final Calendar c = Calendar.getInstance();
         c.setTimeZone(mTimeFormatter.getTimeZone());
-        c.set(Calendar.HOUR_OF_DAY, localTime.hourOfDay);
-        c.set(Calendar.MINUTE, localTime.minute);
+        c.set(Calendar.HOUR_OF_DAY, localTime.getHour());
+        c.set(Calendar.MINUTE, localTime.getMinute());
         c.set(Calendar.SECOND, 0);
         c.set(Calendar.MILLISECOND, 0);
         return mTimeFormatter.format(c.getTime());
@@ -116,12 +117,12 @@
     }
 
     @Override
-    public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
+    public void onCustomStartTimeChanged(LocalTime startTime) {
         updateSummary();
     }
 
     @Override
-    public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
+    public void onCustomEndTimeChanged(LocalTime endTime) {
         updateSummary();
     }
 }
diff --git a/src/com/android/settings/display/NightDisplaySettings.java b/src/com/android/settings/display/NightDisplaySettings.java
index 23ddf07..f9568fb 100644
--- a/src/com/android/settings/display/NightDisplaySettings.java
+++ b/src/com/android/settings/display/NightDisplaySettings.java
@@ -32,6 +32,7 @@
 import com.android.settings.SettingsPreferenceFragment;
 
 import java.text.DateFormat;
+import java.time.LocalTime;
 import java.util.Calendar;
 import java.util.TimeZone;
 
@@ -144,7 +145,7 @@
     @Override
     public Dialog onCreateDialog(final int dialogId) {
         if (dialogId == DIALOG_START_TIME || dialogId == DIALOG_END_TIME) {
-            final NightDisplayController.LocalTime initialTime;
+            final LocalTime initialTime;
             if (dialogId == DIALOG_START_TIME) {
                 initialTime = mController.getCustomStartTime();
             } else {
@@ -156,15 +157,14 @@
             return new TimePickerDialog(context, new TimePickerDialog.OnTimeSetListener() {
                 @Override
                 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
-                    final NightDisplayController.LocalTime time =
-                            new NightDisplayController.LocalTime(hourOfDay, minute);
+                    final LocalTime time = LocalTime.of(hourOfDay, minute);
                     if (dialogId == DIALOG_START_TIME) {
                         mController.setCustomStartTime(time);
                     } else {
                         mController.setCustomEndTime(time);
                     }
                 }
-            }, initialTime.hourOfDay, initialTime.minute, use24HourFormat);
+            }, initialTime.getHour(), initialTime.getMinute(), use24HourFormat);
         }
         return super.onCreateDialog(dialogId);
     }
@@ -201,11 +201,11 @@
         mTemperaturePreference.setProgress(convertTemperature(colorTemperature));
     }
 
-    private String getFormattedTimeString(NightDisplayController.LocalTime localTime) {
+    private String getFormattedTimeString(LocalTime localTime) {
         final Calendar c = Calendar.getInstance();
         c.setTimeZone(mTimeFormatter.getTimeZone());
-        c.set(Calendar.HOUR_OF_DAY, localTime.hourOfDay);
-        c.set(Calendar.MINUTE, localTime.minute);
+        c.set(Calendar.HOUR_OF_DAY, localTime.getHour());
+        c.set(Calendar.MINUTE, localTime.getMinute());
         c.set(Calendar.SECOND, 0);
         c.set(Calendar.MILLISECOND, 0);
         return mTimeFormatter.format(c.getTime());
@@ -221,12 +221,12 @@
     }
 
     @Override
-    public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
+    public void onCustomStartTimeChanged(LocalTime startTime) {
         mStartTimePreference.setSummary(getFormattedTimeString(startTime));
     }
 
     @Override
-    public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
+    public void onCustomEndTimeChanged(LocalTime endTime) {
         mEndTimePreference.setSummary(getFormattedTimeString(endTime));
     }
 
diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java
index e9f8caf..5db256d 100644
--- a/src/com/android/settings/notification/ChannelNotificationSettings.java
+++ b/src/com/android/settings/notification/ChannelNotificationSettings.java
@@ -360,7 +360,9 @@
     }
 
     void updateDependents(boolean banned) {
+        PreferenceGroup parent;
         if (mShowLegacyChannelConfig) {
+            parent = getPreferenceScreen();
             setVisible(mImportanceToggle, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
         } else {
             setVisible(mAdvanced, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
@@ -369,12 +371,13 @@
                     NotificationManager.IMPORTANCE_DEFAULT) && canPulseLight());
             setVisible(mVibrate, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT));
             setVisible(mRingtone, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT));
+            parent = mAdvanced;
         }
-        setVisible(mAdvanced, mBadge, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
-        setVisible(mAdvanced, mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
+        setVisible(parent, mBadge, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
+        setVisible(parent, mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
                 || (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)
                 && mDndVisualEffectsSuppressed));
-        setVisible(mAdvanced, mVisibilityOverride, isLockScreenSecure()
+        setVisible(parent, mVisibilityOverride, isLockScreenSecure()
                 &&checkCanBeVisible(NotificationManager.IMPORTANCE_LOW));
         setVisible(mBlockedDesc, mChannel.getImportance() == IMPORTANCE_NONE);
         if (mAppLink != null) {
diff --git a/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java b/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
index 0e4c542..4838cc6 100644
--- a/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
+++ b/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
@@ -116,6 +116,7 @@
     }
 
     private void initPreferences() {
+        Log.d(TAG, "Rebuilding the preferences");
         PreferenceScreen preferenceScreen = getPreferenceScreen();
         final Context context = getPrefContext();
 
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index b47391d..b143f58 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -1070,7 +1070,7 @@
 
     protected void connect(final WifiConfiguration config, boolean isSavedNetwork) {
         // Log subtype if configuration is a saved network.
-        mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT,
+        mMetricsFeatureProvider.action(getVisibilityLogger(), MetricsEvent.ACTION_WIFI_CONNECT,
                 isSavedNetwork);
         mWifiManager.connect(config, mConnectListener);
         mClickedConnect = true;
diff --git a/tests/robotests/src/android/service/settings/suggestions/Suggestion.java b/tests/robotests/src/android/service/settings/suggestions/Suggestion.java
index df7a8ae..2bb6192 100644
--- a/tests/robotests/src/android/service/settings/suggestions/Suggestion.java
+++ b/tests/robotests/src/android/service/settings/suggestions/Suggestion.java
@@ -16,5 +16,104 @@
 
 package android.service.settings.suggestions;
 
+import android.app.PendingIntent;
+import android.os.Parcel;
+import android.text.TextUtils;
+
 public class Suggestion {
+    private final String mId;
+    private final CharSequence mTitle;
+    private final CharSequence mSummary;
+    private final PendingIntent mPendingIntent;
+
+    /**
+     * Gets the id for the suggestion object.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Title of the suggestion that is shown to the user.
+     */
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Optional summary describing what this suggestion controls.
+     */
+    public CharSequence getSummary() {
+        return mSummary;
+    }
+
+    /**
+     * The Intent to launch when the suggestion is activated.
+     */
+    public PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    private Suggestion(Builder builder) {
+        mTitle = builder.mTitle;
+        mSummary = builder.mSummary;
+        mPendingIntent = builder.mPendingIntent;
+        mId = builder.mId;
+    }
+
+    private Suggestion(Parcel in) {
+        mId = in.readString();
+        mTitle = in.readCharSequence();
+        mSummary = in.readCharSequence();
+        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
+    }
+
+    /**
+     * Builder class for {@link Suggestion}.
+     */
+    public static class Builder {
+        private final String mId;
+        private CharSequence mTitle;
+        private CharSequence mSummary;
+        private PendingIntent mPendingIntent;
+
+        public Builder(String id) {
+            if (TextUtils.isEmpty(id)) {
+                throw new IllegalArgumentException("Suggestion id cannot be empty");
+            }
+            mId = id;
+        }
+
+        /**
+         * Sets suggestion title
+         */
+
+        public Builder setTitle(CharSequence title) {
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets suggestion summary
+         */
+        public Builder setSummary(CharSequence summary) {
+            mSummary = summary;
+            return this;
+        }
+
+        /**
+         * Sets suggestion intent
+         */
+        public Builder setPendingIntent(PendingIntent pendingIntent) {
+            mPendingIntent = pendingIntent;
+            return this;
+        }
+
+        /**
+         * Builds an immutable {@link Suggestion} object.
+         */
+        public Suggestion build() {
+            return new Suggestion(this /* builder */);
+        }
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java b/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java
index 229057f..07acb13 100644
--- a/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
+import android.os.RemoteException;
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
@@ -65,4 +66,11 @@
         verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class),
                 eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_USAGE_VIEW_DENY), eq("app"));
     }
+
+    @Test
+    public void refreshUi_nullPackageInfo_shouldNotCrash() throws RemoteException {
+        mFragment.mPackageInfo = null;
+        mFragment.refreshUi();
+        // should not crash
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryUpdaterTest.java
index e3f00d8..0c27412 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryUpdaterTest.java
@@ -18,17 +18,25 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.util.Log;
 
 import com.android.settings.R;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -39,19 +47,9 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
-import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-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 BluetoothSummaryUpdaterTest {
@@ -70,16 +68,33 @@
     @Mock
     private SummaryListener mListener;
 
+    // Disabled by default
+    private final boolean[] mAdapterEnabled = {false};
+    // Not connected by default
+    private final int[] mAdapterConnectionState = {BluetoothAdapter.STATE_DISCONNECTED};
+    // Not connected by default
+    private final boolean[] mDeviceConnected = {false, false};
+    private final Set<BluetoothDevice> mBondedDevices = new HashSet<>();
     private BluetoothSummaryUpdater mSummaryUpdater;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        when(mBluetoothManager.getBluetoothAdapter()).thenReturn(mBtAdapter);
-        when(mBtAdapter.isEnabled()).thenReturn(true);
-        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTED);
         mContext = RuntimeEnvironment.application.getApplicationContext();
+        doCallRealMethod().when(mListener).onSummaryChanged(anyString());
+        // Setup mock adapter
+        when(mBluetoothManager.getBluetoothAdapter()).thenReturn(mBtAdapter);
+        doAnswer(invocation -> mAdapterEnabled[0]).when(mBtAdapter).isEnabled();
+        doAnswer(invocation -> mAdapterConnectionState[0]).when(mBtAdapter).getConnectionState();
         mSummaryUpdater = new BluetoothSummaryUpdater(mContext, mListener, mBluetoothManager);
+        // Setup first device
+        doReturn(DEVICE_NAME).when(mConnectedDevice).getName();
+        doAnswer(invocation -> mDeviceConnected[0]).when(mConnectedDevice).isConnected();
+        // Setup second device
+        doReturn(DEVICE_KEYBOARD_NAME).when(mConnectedKeyBoardDevice).getName();
+        doAnswer(invocation -> mDeviceConnected[1]).when(mConnectedKeyBoardDevice)
+                .isConnected();
+        doReturn(mBondedDevices).when(mBtAdapter).getBondedDevices();
     }
 
     @Test
@@ -98,7 +113,10 @@
 
     @Test
     public void register_true_shouldSendSummaryChange() {
-        prepareConnectedDevice(false);
+        mAdapterEnabled[0] = true;
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
+        mBondedDevices.add(mConnectedDevice);
+        mDeviceConnected[0] = true;
 
         mSummaryUpdater.register(true);
 
@@ -108,7 +126,11 @@
 
     @Test
     public void onBluetoothStateChanged_btDisabled_shouldSendDisabledSummary() {
-        mSummaryUpdater.register(true);
+        // These states should be ignored
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
+        mBondedDevices.add(mConnectedDevice);
+        mDeviceConnected[0] = true;
+
         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
 
         verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
@@ -116,9 +138,11 @@
 
     @Test
     public void onBluetoothStateChanged_btEnabled_connected_shouldSendConnectedSummary() {
-        prepareConnectedDevice(false);
+        mAdapterEnabled[0] = true;
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
+        mBondedDevices.add(mConnectedDevice);
+        mDeviceConnected[0] = true;
 
-        mSummaryUpdater.register(true);
         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
 
         verify(mListener).onSummaryChanged(
@@ -126,58 +150,71 @@
     }
 
     @Test
+    public void onBluetoothStateChanged_btEnabled_connectedMisMatch_shouldSendNotConnected() {
+        mAdapterEnabled[0] = true;
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
+        mBondedDevices.add(mConnectedDevice);
+        // State mismatch
+        mDeviceConnected[0] = false;
+
+        mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
+    }
+
+    @Test
     public void onBluetoothStateChanged_btEnabled_notConnected_shouldSendDisconnectedMessage() {
-        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
-        mSummaryUpdater.register(true);
+        mAdapterEnabled[0] = true;
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTED;
+        mBondedDevices.add(mConnectedDevice);
+        // This should be ignored
+        mDeviceConnected[0] = true;
+
         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
 
-        verify(mListener).onSummaryChanged(
-                mContext.getString(R.string.disconnected));
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
     }
 
     @Test
     public void onBluetoothStateChanged_ConnectedDisabledEnabled_shouldSendDisconnectedSummary() {
-        final boolean[] connected = {false};
-        final List<CachedBluetoothDevice> devices = new ArrayList<>();
-        devices.add(mock(CachedBluetoothDevice.class));
-        doAnswer(invocation -> connected[0]).when(devices.get(0)).isConnected();
-        when(mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy())
-                .thenReturn(devices);
-        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
-        prepareConnectedDevice(false);
+        mAdapterEnabled[0] = true;
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTED;
+        mBondedDevices.add(mConnectedDevice);
+        mDeviceConnected[0] = false;
 
         mSummaryUpdater.register(true);
         verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
 
-        connected[0] = true;
-        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTED);
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
+        mDeviceConnected[0] = true;
         mSummaryUpdater.onConnectionStateChanged(null /* device */,
                 BluetoothAdapter.STATE_CONNECTED);
         verify(mListener).onSummaryChanged(
                 mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
 
+        mAdapterEnabled[0] = false;
         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
         verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
 
-        connected[0] = false;
+        // Turning ON means not enabled
         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
+        // There should still be only one invocation of disabled message
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
+
+        mAdapterEnabled[0] = true;
+        mDeviceConnected[0] = false;
+        mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
         verify(mListener, times(2)).onSummaryChanged(mContext.getString(R.string.disconnected));
         verify(mListener, times(4)).onSummaryChanged(anyString());
     }
 
     @Test
     public void onConnectionStateChanged_connected_shouldSendConnectedMessage() {
-        final List<CachedBluetoothDevice> devices = new ArrayList<>();
-        devices.add(mock(CachedBluetoothDevice.class));
-        when(devices.get(0).isConnected()).thenReturn(true);
-        when(mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy())
-                .thenReturn(devices);
-        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
-        prepareConnectedDevice(false);
+        mAdapterEnabled[0] = true;
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
+        mBondedDevices.add(mConnectedDevice);
+        mDeviceConnected[0] = true;
 
-        mSummaryUpdater.register(true);
-
-        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTED);
         mSummaryUpdater.onConnectionStateChanged(null /* device */,
                 BluetoothAdapter.STATE_CONNECTED);
 
@@ -187,7 +224,22 @@
 
     @Test
     public void onConnectionStateChanged_inconsistentState_shouldSendDisconnectedMessage() {
-        mSummaryUpdater.register(true);
+        mAdapterEnabled[0] = true;
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTED;
+        mBondedDevices.add(mConnectedDevice);
+        mDeviceConnected[0] = false;
+
+        mSummaryUpdater.onConnectionStateChanged(null /* device */,
+                BluetoothAdapter.STATE_CONNECTED);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
+    }
+
+    @Test
+    public void onConnectionStateChanged_noBondedDevice_shouldSendDisconnectedMessage() {
+        mAdapterEnabled[0] = true;
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
+
         mSummaryUpdater.onConnectionStateChanged(null /* device */,
                 BluetoothAdapter.STATE_CONNECTED);
 
@@ -197,8 +249,10 @@
 
     @Test
     public void onConnectionStateChanged_connecting_shouldSendConnectingMessage() {
-        mSummaryUpdater.register(true);
-        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
+        // No need for bonded devices
+        mAdapterEnabled[0] = true;
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTING;
+
         mSummaryUpdater.onConnectionStateChanged(null /* device */,
                 BluetoothAdapter.STATE_CONNECTING);
 
@@ -207,8 +261,10 @@
 
     @Test
     public void onConnectionStateChanged_disconnecting_shouldSendDisconnectingMessage() {
-        mSummaryUpdater.register(true);
-        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTING);
+        // No need for bonded devices
+        mAdapterEnabled[0] = true;
+        mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTING;
+
         mSummaryUpdater.onConnectionStateChanged(null /* device */,
                 BluetoothAdapter.STATE_DISCONNECTING);
 
@@ -217,7 +273,8 @@
 
     @Test
     public void getConnectedDeviceSummary_hasConnectedDevice_returnOneDeviceSummary() {
-        prepareConnectedDevice(false);
+        mBondedDevices.add(mConnectedDevice);
+        mDeviceConnected[0] = true;
         final String expectedSummary = mContext.getString(R.string.bluetooth_connected_summary,
                 DEVICE_NAME);
 
@@ -226,28 +283,16 @@
 
     @Test
     public void getConnectedDeviceSummary_multipleDevices_returnMultipleDevicesSummary() {
-        prepareConnectedDevice(true);
+        mBondedDevices.add(mConnectedDevice);
+        mBondedDevices.add(mConnectedKeyBoardDevice);
+        mDeviceConnected[0] = true;
+        mDeviceConnected[1] = true;
         final String expectedSummary = mContext.getString(
                 R.string.bluetooth_connected_multiple_devices_summary);
 
         assertThat(mSummaryUpdater.getConnectedDeviceSummary()).isEqualTo(expectedSummary);
     }
 
-    private void prepareConnectedDevice(boolean multipleDevices) {
-        final Set<BluetoothDevice> devices = new HashSet<>();
-        doReturn(DEVICE_NAME).when(mConnectedDevice).getName();
-        doReturn(true).when(mConnectedDevice).isConnected();
-        devices.add(mConnectedDevice);
-        if (multipleDevices) {
-            // Add one more device if we need to test multiple devices
-            doReturn(DEVICE_KEYBOARD_NAME).when(mConnectedKeyBoardDevice).getName();
-            doReturn(true).when(mConnectedKeyBoardDevice).isConnected();
-            devices.add(mConnectedKeyBoardDevice);
-        }
-
-        doReturn(devices).when(mBtAdapter).getBondedDevices();
-    }
-
     private class SummaryListener implements OnSummaryChangeListener {
         String summary;
 
diff --git a/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java b/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java
index ea33c83..ff91c40 100644
--- a/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java
@@ -28,6 +28,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
@@ -42,24 +44,35 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class MetricsFeatureProviderTest {
+    private static int CATEGORY = 10;
+    private static boolean SUBTYPE_BOOLEAN = true;
+    private static int SUBTYPE_INTEGER = 1;
+    private static long ELAPSED_TIME = 1000;
 
-    @Mock
-    private LogWriter mLogWriter;
+    @Mock private LogWriter mockLogWriter;
+    @Mock private VisibilityLoggerMixin mockVisibilityLogger;
+
     private Context mContext;
     private MetricsFeatureProvider mProvider;
 
+    @Captor
+    private ArgumentCaptor<Pair> mPairCaptor;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
         mProvider = new MetricsFeatureProvider();
         List<LogWriter> writers = new ArrayList<>();
-        writers.add(mLogWriter);
+        writers.add(mockLogWriter);
         ReflectionHelpers.setField(mProvider, "mLoggerWriters", writers);
+
+        when(mockVisibilityLogger.elapsedTimeSinceVisible()).thenReturn(ELAPSED_TIME);
     }
 
     @Test
@@ -77,7 +90,7 @@
         mProvider.logDashboardStartIntent(mContext, null /* intent */,
                 MetricsEvent.SETTINGS_GESTURES);
 
-        verifyNoMoreInteractions(mLogWriter);
+        verifyNoMoreInteractions(mockLogWriter);
     }
 
     @Test
@@ -86,7 +99,7 @@
 
         mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
 
-        verify(mLogWriter).action(
+        verify(mockLogWriter).action(
                 eq(mContext),
                 eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK),
                 anyString(),
@@ -99,10 +112,32 @@
 
         mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
 
-        verify(mLogWriter).action(
+        verify(mockLogWriter).action(
                 eq(mContext),
                 eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK),
                 anyString(),
                 eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES)));
     }
+
+    @Test
+    public void action_BooleanLogsElapsedTime() {
+        mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_BOOLEAN);
+        verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_BOOLEAN), mPairCaptor.capture());
+
+        Pair value = mPairCaptor.getValue();
+        assertThat(value.first instanceof Integer).isTrue();
+        assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS);
+        assertThat(value.second).isEqualTo(ELAPSED_TIME);
+    }
+
+    @Test
+    public void action_IntegerLogsElapsedTime() {
+        mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_INTEGER);
+        verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_INTEGER), mPairCaptor.capture());
+
+        Pair value = mPairCaptor.getValue();
+        assertThat(value.first instanceof Integer).isTrue();
+        assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS);
+        assertThat(value.second).isEqualTo(ELAPSED_TIME);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
index 4b345d0..b14a80f 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -35,6 +36,7 @@
 import android.content.res.TypedArray;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
 import android.support.v7.widget.RecyclerView;
 import android.util.DisplayMetrics;
 import android.util.Pair;
@@ -558,6 +560,33 @@
     }
 
     @Test
+    public void testBindConditionAndSuggestion_v2_shouldSetSuggestionAdapterAndNoCrash() {
+        mDashboardAdapter = new DashboardAdapter(mContext, null, null, null, null);
+        final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
+        final DashboardCategory category = mock(DashboardCategory.class);
+        final List<Tile> tiles = new ArrayList<>();
+        tiles.add(mock(Tile.class));
+        category.tiles = tiles;
+
+        mDashboardAdapter.setSuggestionsV2(suggestions);
+
+        final RecyclerView data = mock(RecyclerView.class);
+        when(data.getResources()).thenReturn(mResources);
+        when(data.getContext()).thenReturn(mContext);
+        when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
+        final View itemView = mock(View.class);
+        when(itemView.findViewById(R.id.data)).thenReturn(data);
+        final DashboardAdapter.SuggestionAndConditionContainerHolder holder =
+                new DashboardAdapter.SuggestionAndConditionContainerHolder(itemView);
+
+        mDashboardAdapter.onBindConditionAndSuggestion(
+                holder, DashboardAdapter.SUGGESTION_CONDITION_HEADER_POSITION);
+
+        verify(data).setAdapter(any(SuggestionAdapter.class));
+        // should not crash
+    }
+
+    @Test
     public void testBindConditionAndSuggestion_emptySuggestion_shouldSetConditionAdpater() {
         final Bundle savedInstance = new Bundle();
         savedInstance.putInt(DashboardAdapter.STATE_SUGGESTION_CONDITION_MODE,
@@ -598,6 +627,17 @@
         return suggestions;
     }
 
+    private List<Suggestion> makeSuggestionsV2(String... pkgNames) {
+        final List<Suggestion> suggestions = new ArrayList<>();
+        for (String pkgName : pkgNames) {
+            final Suggestion suggestion = new Suggestion.Builder(pkgName)
+                    .setPendingIntent(mock(PendingIntent.class))
+                    .build();
+            suggestions.add(suggestion);
+        }
+        return suggestions;
+    }
+
     private void setupSuggestions(List<Tile> suggestions) {
         mDashboardAdapter.setCategoriesAndSuggestions(null /* category */, suggestions);
         final Context context = RuntimeEnvironment.application;
@@ -605,4 +645,6 @@
                 LayoutInflater.from(context).inflate(
                         R.layout.suggestion_condition_header, new RelativeLayout(context), true));
     }
+
+
 }
diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java
index bd3650b..6b80465 100644
--- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java
@@ -15,13 +15,23 @@
  */
 package com.android.settings.dashboard.suggestions;
 
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
+import android.service.settings.suggestions.Suggestion;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ContextThemeWrapper;
 import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.FrameLayout;
@@ -31,15 +41,12 @@
 
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.dashboard.DashboardAdapter;
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settingslib.drawer.Tile;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,22 +57,13 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowApplication;
 
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.verify;
+import java.util.ArrayList;
+import java.util.List;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class SuggestionAdapterTest {
-    @Mock
-    private Tile mSuggestion1;
-    @Mock
-    private Tile mSuggestion2;
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private SettingsActivity mActivity;
 
@@ -74,6 +72,8 @@
     private DashboardAdapter.DashboardItemHolder mSuggestionHolder;
     private List<Tile> mOneSuggestion;
     private List<Tile> mTwoSuggestions;
+    private List<Suggestion> mOneSuggestionV2;
+    private List<Suggestion> mTwoSuggestionsV2;
 
     @Before
     public void setUp() {
@@ -81,39 +81,75 @@
         mContext = RuntimeEnvironment.application;
         FakeFeatureFactory.setupForTest(mActivity);
 
-        mSuggestion1.title = "Test Suggestion 1";
-        mSuggestion1.icon = mock(Icon.class);
-        mSuggestion2.title = "Test Suggestion 2";
-        mSuggestion2.icon = mock(Icon.class);
+        final Tile suggestion1 = new Tile();
+        final Tile suggestion2 = new Tile();
+        final Suggestion suggestion1V2 = new Suggestion.Builder("id1")
+                .setTitle("Test suggestion 1")
+                .build();
+        final Suggestion suggestion2V2 = new Suggestion.Builder("id2")
+                .setTitle("Test suggestion 2")
+                .build();
+        suggestion1.title = "Test Suggestion 1";
+        suggestion1.icon = mock(Icon.class);
+        suggestion2.title = "Test Suggestion 2";
+        suggestion2.icon = mock(Icon.class);
         mOneSuggestion = new ArrayList<>();
-        mOneSuggestion.add(mSuggestion1);
+        mOneSuggestion.add(suggestion1);
         mTwoSuggestions = new ArrayList<>();
-        mTwoSuggestions.add(mSuggestion1);
-        mTwoSuggestions.add(mSuggestion2);
+        mTwoSuggestions.add(suggestion1);
+        mTwoSuggestions.add(suggestion2);
+        mOneSuggestionV2 = new ArrayList<>();
+        mOneSuggestionV2.add(suggestion1V2);
+        mTwoSuggestionsV2 = new ArrayList<>();
+        mTwoSuggestionsV2.add(suggestion1V2);
+        mTwoSuggestionsV2.add(suggestion2V2);
     }
 
     @Test
     public void getItemCount_shouldReturnListSize() {
-        mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>());
+        mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion,
+                null /* suggestionV2 */, new ArrayList<>());
         assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1);
 
-        mSuggestionAdapter = new SuggestionAdapter(mContext, mTwoSuggestions, new ArrayList<>());
+        mSuggestionAdapter = new SuggestionAdapter(mContext, mTwoSuggestions,
+                null /* suggestionV2 */, new ArrayList<>());
+        assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void getItemCount_v2_shouldReturnListSize() {
+        mSuggestionAdapter = new SuggestionAdapter(mContext, null /* suggestions */,
+                mOneSuggestionV2, new ArrayList<>());
+        assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1);
+
+        mSuggestionAdapter = new SuggestionAdapter(mContext, null /* suggestions */,
+                mTwoSuggestionsV2, new ArrayList<>());
         assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2);
     }
 
     @Test
     public void getItemViewType_shouldReturnSuggestionTile() {
-        mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>());
+        mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion,
+                null /* suggestionV2 */, new ArrayList<>());
         assertThat(mSuggestionAdapter.getItemViewType(0))
-            .isEqualTo(R.layout.suggestion_tile);
+                .isEqualTo(R.layout.suggestion_tile);
+    }
+
+    @Test
+    public void getItemViewType_v2_shouldReturnSuggestionTile() {
+        mSuggestionAdapter = new SuggestionAdapter(mContext, null /* suggestions */,
+                mOneSuggestionV2, new ArrayList<>());
+        assertThat(mSuggestionAdapter.getItemViewType(0))
+                .isEqualTo(R.layout.suggestion_tile);
     }
 
     @Test
     public void onBindViewHolder_shouldSetListener() {
         final View view = spy(LayoutInflater.from(mContext).inflate(
-            R.layout.suggestion_tile, new LinearLayout(mContext), true));
+                R.layout.suggestion_tile, new LinearLayout(mContext), true));
         mSuggestionHolder = new DashboardAdapter.DashboardItemHolder(view);
-        mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>());
+        mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion,
+                null /* suggestionV2 */, new ArrayList<>());
 
         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
 
@@ -127,7 +163,7 @@
         TextView textView = new TextView(RuntimeEnvironment.application);
         doReturn(textView).when(remoteViews).apply(any(Context.class), any(ViewGroup.class));
         packages.get(0).remoteViews = remoteViews;
-        setupSuggestions(mActivity, packages);
+        setupSuggestions(mActivity, packages, null);
 
         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
 
@@ -150,7 +186,7 @@
         layout.addView(primary);
         doReturn(layout).when(remoteViews).apply(any(Context.class), any(ViewGroup.class));
         packages.get(0).remoteViews = remoteViews;
-        setupSuggestions(mActivity, packages);
+        setupSuggestions(mActivity, packages, null /* suggestionV2 */);
 
         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
         mSuggestionHolder.itemView.performClick();
@@ -164,6 +200,18 @@
     }
 
     @Test
+    public void onBindViewHolder_v2_itemViewShouldHandleClick()
+            throws PendingIntent.CanceledException {
+        final List<Suggestion> packages = makeSuggestionsV2("pkg1");
+        setupSuggestions(mActivity, null /* suggestionV1 */, packages);
+
+        mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
+        mSuggestionHolder.itemView.performClick();
+
+        verify(packages.get(0).getPendingIntent()).send();
+    }
+
+    @Test
     public void onBindViewHolder_viewsShouldClearOnRebind() {
         Context context =
                 new ContextThemeWrapper(RuntimeEnvironment.application, R.style.Theme_Settings);
@@ -176,7 +224,7 @@
         layout.addView(primary);
         doReturn(layout).when(remoteViews).apply(any(Context.class), any(ViewGroup.class));
         packages.get(0).remoteViews = remoteViews;
-        setupSuggestions(mActivity, packages);
+        setupSuggestions(mActivity, packages, null /* suggestionV2 */);
 
         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
@@ -185,8 +233,26 @@
         assertThat(itemView.getChildCount()).isEqualTo(1);
     }
 
-    private void setupSuggestions(Context context, List<Tile> suggestions) {
-        mSuggestionAdapter = new SuggestionAdapter(context, suggestions, new ArrayList<>());
+    @Test
+    public void getSuggestionsV2_shouldReturnSuggestionWhenMatch() {
+        final List<Suggestion> suggestionsV2 = makeSuggestionsV2("pkg1");
+        setupSuggestions(mActivity, null /* suggestionV1 */, suggestionsV2);
+
+        assertThat(mSuggestionAdapter.getSuggestion(0)).isNull();
+        assertThat(mSuggestionAdapter.getSuggestionsV2(0)).isNotNull();
+
+        List<Tile> suggestionsV1 = makeSuggestions("pkg1");
+        setupSuggestions(mActivity, suggestionsV1, null /* suggestionV2 */);
+
+        assertThat(mSuggestionAdapter.getSuggestionsV2(0)).isNull();
+        assertThat(mSuggestionAdapter.getSuggestion(0)).isNotNull();
+
+    }
+
+    private void setupSuggestions(Context context, List<Tile> suggestions,
+            List<Suggestion> suggestionsV2) {
+        mSuggestionAdapter = new SuggestionAdapter(context, suggestions, suggestionsV2,
+                new ArrayList<>());
         mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
                 new FrameLayout(RuntimeEnvironment.application),
                 mSuggestionAdapter.getItemViewType(0));
@@ -204,4 +270,14 @@
         return suggestions;
     }
 
+    private List<Suggestion> makeSuggestionsV2(String... pkgNames) {
+        final List<Suggestion> suggestions = new ArrayList<>();
+        for (String pkgName : pkgNames) {
+            final Suggestion suggestion = new Suggestion.Builder(pkgName)
+                    .setPendingIntent(mock(PendingIntent.class))
+                    .build();
+            suggestions.add(suggestion);
+        }
+        return suggestions;
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java
index e118478..e42466c 100644
--- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java
@@ -17,14 +17,15 @@
 package com.android.settings.dashboard.suggestions;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.LoaderManager;
 import android.content.Context;
-import android.util.FeatureFlagUtils;
 
 import com.android.settings.TestConfig;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
-import com.android.settings.testutils.shadow.SettingsShadowSystemProperties;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.After;
@@ -38,13 +39,14 @@
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
         shadows = {
-                SettingsShadowSystemProperties.class,
                 ShadowSuggestionController.class
         })
 public class SuggestionControllerMixinTest {
 
     @Mock
     private Context mContext;
+    @Mock
+    private SuggestionControllerMixin.SuggestionControllerHost mHost;
     private Lifecycle mLifecycle;
     private SuggestionControllerMixin mMixin;
 
@@ -53,33 +55,16 @@
         MockitoAnnotations.initMocks(this);
         mLifecycle = new Lifecycle();
         when(mContext.getApplicationContext()).thenReturn(mContext);
-        SettingsShadowSystemProperties.set(
-                FeatureFlagUtils.FFLAG_PREFIX + SuggestionControllerMixin.FEATURE_FLAG, "true");
     }
 
     @After
     public void tearDown() {
         ShadowSuggestionController.reset();
-        SettingsShadowSystemProperties.clear();
     }
 
     @Test
-    public void systemPropertySet_verifyIsEnabled() {
-        SettingsShadowSystemProperties.set(
-                FeatureFlagUtils.FFLAG_PREFIX + SuggestionControllerMixin.FEATURE_FLAG, "true");
-        assertThat(SuggestionControllerMixin.isEnabled()).isTrue();
-    }
-
-    @Test
-    public void systemPropertyNotSet_verifyIsDisabled() {
-        SettingsShadowSystemProperties.set(
-                FeatureFlagUtils.FFLAG_PREFIX + SuggestionControllerMixin.FEATURE_FLAG, "false");
-        assertThat(SuggestionControllerMixin.isEnabled()).isFalse();
-    }
-
-    @Test
-    public void goThroughLifecycle_onStartStop_shouldStartStopService() {
-        mMixin = new SuggestionControllerMixin(mContext, mLifecycle);
+    public void goThroughLifecycle_onStartStop_shouldStartStopController() {
+        mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle);
 
         mLifecycle.onStart();
         assertThat(ShadowSuggestionController.sStartCalled).isTrue();
@@ -90,10 +75,13 @@
 
     @Test
     public void onServiceConnected_shouldGetSuggestion() {
-        mMixin = new SuggestionControllerMixin(mContext, mLifecycle);
+        final LoaderManager loaderManager = mock(LoaderManager.class);
+        when(mHost.getLoaderManager()).thenReturn(loaderManager);
+
+        mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle);
         mMixin.onServiceConnected();
 
-        assertThat(ShadowSuggestionController.sGetSuggestionCalled).isTrue();
+        verify(loaderManager).restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
+                null /* args */, mMixin /* callback */);
     }
-
 }
diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java
index c343f97..0650d5b 100644
--- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java
@@ -35,6 +35,7 @@
 import android.content.pm.PackageManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.provider.Settings.Secure;
+import android.util.FeatureFlagUtils;
 import android.util.Pair;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -54,10 +55,12 @@
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settings.testutils.shadow.SettingsShadowSystemProperties;
 import com.android.settings.testutils.shadow.ShadowSecureSettings;
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.suggestions.SuggestionParser;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -69,6 +72,7 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -76,7 +80,10 @@
 @Config(
     manifest = TestConfig.MANIFEST_PATH,
     sdk = TestConfig.SDK_VERSION,
-    shadows = {ShadowSecureSettings.class, SettingsShadowResources.class}
+    shadows = {ShadowSecureSettings.class,
+            SettingsShadowResources.class,
+            SettingsShadowSystemProperties.class
+    }
 )
 public class SuggestionFeatureProviderImplTest {
 
@@ -122,6 +129,11 @@
         mProvider = new SuggestionFeatureProviderImpl(mContext);
     }
 
+    @After
+    public void tearDown() {
+        SettingsShadowSystemProperties.clear();
+    }
+
     @Test
     public void isSuggestionCompleted_doubleTapPower_trueWhenNotAvailable() {
         SettingsShadowResources.overrideResource(
@@ -289,6 +301,23 @@
     }
 
     @Test
+    public void isSuggestionV2Enabled_isNotLowMemoryDevice_sysPropOn_shouldReturnTrue() {
+        when(mActivityManager.isLowRamDevice()).thenReturn(false);
+        SettingsShadowSystemProperties.set(
+                FeatureFlagUtils.FFLAG_PREFIX + mProvider.FEATURE_FLAG_SUGGESTIONS_V2, "true");
+        assertThat(mProvider.isSuggestionV2Enabled(mContext)).isTrue();
+    }
+
+    @Test
+    public void isSuggestionV2Enabled_isNotLowMemoryDevice_sysPropOff_shouldReturnTrue() {
+        when(mActivityManager.isLowRamDevice()).thenReturn(false);
+        SettingsShadowSystemProperties.set(
+                FeatureFlagUtils.FFLAG_PREFIX + mProvider.FEATURE_FLAG_SUGGESTIONS_V2, "false");
+        assertThat(mProvider.isSuggestionV2Enabled(mContext)).isFalse();
+    }
+
+
+    @Test
     public void dismissSuggestion_noParserOrSuggestion_noop() {
         mProvider.dismissSuggestion(mContext, null, null);
         mProvider.dismissSuggestion(mContext, mSuggestionParser, null);
@@ -402,14 +431,35 @@
     }
 
     @Test
-    public void hasUsedNightDisplay_returnsTrue_ifPreviouslyActivated() {
-        Secure.putLong(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, 1L);
+    public void hasUsedNightDisplay_returnsTrue_ifPreviouslyActivatedAndManual() {
+        Secure.putString(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                LocalDateTime.now().toString());
+        Secure.putInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, 1);
         assertThat(mProvider.hasUsedNightDisplay(mContext)).isTrue();
     }
 
     @Test
     public void nightDisplaySuggestion_isCompleted_ifPreviouslyActivated() {
-        Secure.putLong(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, 1L);
+        Secure.putString(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                LocalDateTime.now().toString());
+        final ComponentName componentName =
+                new ComponentName(mContext, NightDisplaySuggestionActivity.class);
+        assertThat(mProvider.isSuggestionCompleted(mContext, componentName)).isTrue();
+    }
+
+    @Test
+    public void nightDisplaySuggestion_isCompleted_ifNonManualMode() {
+        Secure.putInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, 1);
+        final ComponentName componentName =
+                new ComponentName(mContext, NightDisplaySuggestionActivity.class);
+        assertThat(mProvider.isSuggestionCompleted(mContext, componentName)).isTrue();
+    }
+
+    @Test
+    public void nightDisplaySuggestion_isCompleted_ifPreviouslyCleared() {
+        Secure.putString(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                null);
+        Secure.putInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, 1);
         final ComponentName componentName =
                 new ComponentName(mContext, NightDisplaySuggestionActivity.class);
         assertThat(mProvider.isSuggestionCompleted(mContext, componentName)).isTrue();
diff --git a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
index a001aaf..13f7374 100644
--- a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
@@ -17,7 +17,11 @@
 package com.android.settings.development;
 
 import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -108,6 +112,9 @@
     }
 
     @Test
+    @Config(shadows = {
+            ShadowPictureColorModePreferenceController.class
+    })
     public void searchIndex_pageEnabled_shouldNotAddKeysToNonIndexable() {
         final Context appContext = RuntimeEnvironment.application;
         DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(appContext, true);
@@ -161,6 +168,24 @@
                 .isFalse();
     }
 
+    @Test
+    public void onOemUnlockDialogConfirmed_shouldCallControllerOemConfirmed() {
+        final OemUnlockPreferenceController controller = mock(OemUnlockPreferenceController.class);
+        doReturn(controller).when(mDashboard).getDevelopmentOptionsController(
+                OemUnlockPreferenceController.class);
+        mDashboard.onOemUnlockDialogConfirmed();
+        verify(controller).onOemUnlockConfirmed();
+    }
+
+    @Test
+    public void onOemUnlockDialogConfirmed_shouldCallControllerOemDismissed() {
+        final OemUnlockPreferenceController controller = mock(OemUnlockPreferenceController.class);
+        doReturn(controller).when(mDashboard).getDevelopmentOptionsController(
+                OemUnlockPreferenceController.class);
+        mDashboard.onOemUnlockDialogDismissed();
+        verify(controller).onOemUnlockDismissed();
+    }
+
     @Implements(EnableDevelopmentSettingWarningDialog.class)
     public static class ShadowEnableDevelopmentSettingWarningDialog {
 
@@ -176,4 +201,13 @@
             mShown = true;
         }
     }
+
+    @Implements(PictureColorModePreferenceController.class)
+    public static class ShadowPictureColorModePreferenceController {
+
+        @Implementation
+        public boolean isAvailable() {
+            return true;
+        }
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/development/OemUnlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/OemUnlockPreferenceControllerTest.java
new file mode 100644
index 0000000..1367870
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/OemUnlockPreferenceControllerTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.development;
+
+import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes
+        .REQUEST_CODE_ENABLE_OEM_UNLOCK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.os.UserManager;
+import android.service.oemlock.OemLockManager;
+import android.support.v7.preference.PreferenceScreen;
+import android.telephony.TelephonyManager;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.RestrictedSwitchPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class OemUnlockPreferenceControllerTest {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private Activity mActivity;
+    @Mock
+    private DevelopmentSettingsDashboardFragment mFragment;
+    @Mock
+    private RestrictedSwitchPreference mPreference;
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+    @Mock
+    private OemLockManager mOemLockManager;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private Resources mResources;
+    private OemUnlockPreferenceController mController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.OEM_LOCK_SERVICE)).thenReturn(mOemLockManager);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+        when(mContext.getResources()).thenReturn(mResources);
+        mController = new OemUnlockPreferenceController(mContext, mActivity, mFragment);
+        when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn(
+                mPreference);
+        mController.displayPreference(mPreferenceScreen);
+    }
+
+    @Test
+    public void isAvailable_shouldReturnTrueWhenOemLockManagerIsNotNull() {
+        boolean returnValue = mController.isAvailable();
+
+        assertThat(returnValue).isTrue();
+    }
+
+    @Test
+    public void isAvailable_shouldReturnFalseWhenOemLockManagerIsNull() {
+        when(mContext.getSystemService(Context.OEM_LOCK_SERVICE)).thenReturn(null);
+        mController = new OemUnlockPreferenceController(mContext, mActivity, mFragment);
+        boolean returnValue = mController.isAvailable();
+
+        assertThat(returnValue).isFalse();
+    }
+
+    @Test
+    public void onPreferenceChanged_turnOnUnlock() {
+        mController = spy(mController);
+        doReturn(false).when(mController).showKeyguardConfirmation(mResources,
+                REQUEST_CODE_ENABLE_OEM_UNLOCK);
+        doNothing().when(mController).confirmEnableOemUnlock();
+        mController.onPreferenceChange(null, true);
+
+        verify(mController).confirmEnableOemUnlock();
+    }
+
+    @Test
+    public void onPreferenceChanged_turnOffUnlock() {
+        mController.onPreferenceChange(null, false);
+
+        verify(mOemLockManager).setOemUnlockAllowedByUser(false);
+    }
+
+    @Test
+    public void updateState_preferenceShouldBeCheckedAndShouldBeDisabled() {
+        mController = spy(mController);
+        when(mOemLockManager.isOemUnlockAllowed()).thenReturn(true);
+        doReturn(true).when(mController).isOemUnlockAllowedByUserAndCarrier();
+        when(mOemLockManager.isDeviceOemUnlocked()).thenReturn(true);
+        mController.updateState(mPreference);
+
+        verify(mPreference).setChecked(true);
+        verify(mPreference).setEnabled(false);
+    }
+
+    @Test
+    public void updateState_preferenceShouldBeUncheckedAndShouldBeDisabled() {
+        mController = spy(mController);
+        when(mOemLockManager.isOemUnlockAllowed()).thenReturn(false);
+        doReturn(true).when(mController).isOemUnlockAllowedByUserAndCarrier();
+        when(mOemLockManager.isDeviceOemUnlocked()).thenReturn(true);
+        mController.updateState(mPreference);
+
+        verify(mPreference).setChecked(false);
+        verify(mPreference).setEnabled(false);
+    }
+
+    @Test
+    public void updateState_preferenceShouldBeCheckedAndShouldBeEnabled() {
+        mController = spy(mController);
+        when(mOemLockManager.isOemUnlockAllowed()).thenReturn(true);
+        doReturn(true).when(mController).isOemUnlockAllowedByUserAndCarrier();
+        when(mOemLockManager.isDeviceOemUnlocked()).thenReturn(false);
+        mController.updateState(mPreference);
+
+        verify(mPreference).setChecked(true);
+        verify(mPreference).setEnabled(true);
+    }
+
+    @Test
+    public void onActivityResult_shouldReturnTrue() {
+        final boolean result = mController.onActivityResult(REQUEST_CODE_ENABLE_OEM_UNLOCK,
+                Activity.RESULT_OK, null);
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void onActivityResult_shouldReturnFalse() {
+        final boolean result = mController.onActivityResult(123454,
+                1434, null);
+
+        assertThat(result).isFalse();
+    }
+
+    @Test
+    public void onDeveloperOptionsEnabled_preferenceShouldCheckRestriction() {
+        mController = spy(mController);
+        doReturn(false).when(mController).isOemUnlockAllowedByUserAndCarrier();
+        when(mPreference.isEnabled()).thenReturn(true);
+        mController.onDeveloperOptionsEnabled();
+
+        verify(mPreference).checkRestrictionAndSetDisabled(UserManager.DISALLOW_FACTORY_RESET);
+
+    }
+
+    @Test
+    public void onDeveloperOptionsDisabled_preferenceShouldCheckRestriction() {
+        mController = spy(mController);
+        doReturn(false).when(mController).isOemUnlockAllowedByUserAndCarrier();
+        when(mPreference.isEnabled()).thenReturn(true);
+        mController.onDeveloperOptionsDisabled();
+
+        verify(mPreference).checkRestrictionAndSetDisabled(UserManager.DISALLOW_FACTORY_RESET);
+
+    }
+
+    @Test
+    public void onOemUnlockConfirmed_oemManagerShouldSetUnlockAllowedByUser() {
+        mController.onOemUnlockConfirmed();
+
+        verify(mOemLockManager).setOemUnlockAllowedByUser(true);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/development/PictureColorModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/PictureColorModePreferenceControllerTest.java
new file mode 100644
index 0000000..5cf4e10
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/PictureColorModePreferenceControllerTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.development;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PictureColorModePreferenceControllerTest {
+
+    @Mock
+    private ColorModePreference mPreference;
+    @Mock
+    private Context mContext;
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+    @Mock
+    private Resources mResources;
+
+    private Lifecycle mLifecycle;
+    private PictureColorModePreferenceController mController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mLifecycle = new Lifecycle();
+        mController = new PictureColorModePreferenceController(mContext, mLifecycle);
+        when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn(
+                mPreference);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getIntArray(R.array.color_mode_ids)).thenReturn(new int[0]);
+        mController.displayPreference(mPreferenceScreen);
+    }
+
+    @Test
+    public void isAvailable_shouldReturnFalseWhenWideColorGambit() {
+        mController = spy(mController);
+        doReturn(2).when(mController).getColorModeDescriptionsSize();
+        doReturn(true).when(mController).isWideColorGamut();
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_shouldReturnTrueWhenNotWideColorGambit() {
+        mController = spy(mController);
+        doReturn(2).when(mController).getColorModeDescriptionsSize();
+        doReturn(false).when(mController).isWideColorGamut();
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_shouldReturnFalseWhenColorCountIsOne() {
+        mController = spy(mController);
+        doReturn(1).when(mController).getColorModeDescriptionsSize();
+        doReturn(true).when(mController).isWideColorGamut();
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_shouldReturnTrueWhenColorCountIsTwo() {
+        mController = spy(mController);
+        doReturn(2).when(mController).getColorModeDescriptionsSize();
+        doReturn(false).when(mController).isWideColorGamut();
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void onDeveloperOptionEnabled_shouldEnablePreference() {
+        mController = spy(mController);
+        doReturn(true).when(mController).isAvailable();
+        mController.onDeveloperOptionsEnabled();
+
+        verify(mPreference).setEnabled(true);
+    }
+
+    @Test
+    public void onDeveloperOptionDisabled_shouldDisablePreference() {
+        mController = spy(mController);
+        doReturn(true).when(mController).isAvailable();
+        mController.onDeveloperOptionsDisabled();
+
+        verify(mPreference).setEnabled(false);
+    }
+
+    @Test
+    public void onResume_shouldStartListening() {
+        mLifecycle.onResume();
+
+        verify(mPreference).startListening();
+    }
+
+    @Test
+    public void onPause_shouldStopListening() {
+        mLifecycle.onPause();
+
+        verify(mPreference).stopListening();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java
index 0f61a5d..8b2a27b 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java
@@ -36,7 +36,11 @@
         int userHandle) {
         final Table<Integer, String, Object> userTable = getUserTable(resolver);
         synchronized (userTable) {
-            userTable.put(userHandle, name, value);
+            if (value != null) {
+                userTable.put(userHandle, name, value);
+            } else {
+                userTable.remove(userHandle, name);
+            }
             return true;
         }
     }