Merge "Add force SmartDark dev setting"
diff --git a/Android.mk b/Android.mk
index ca2ad4a..04749bf 100644
--- a/Android.mk
+++ b/Android.mk
@@ -43,6 +43,7 @@
     settings-contextual-card-protos-lite \
     contextualcards \
     settings-logtags \
+    zxing-core-1.7
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
diff --git a/res/layout/homepage_slice_tile.xml b/res/layout/homepage_slice_tile.xml
index 28cdfb1..dbdb91b 100644
--- a/res/layout/homepage_slice_tile.xml
+++ b/res/layout/homepage_slice_tile.xml
@@ -21,13 +21,55 @@
     android:layout_height="wrap_content"
     style="@style/ContextualCardStyle">
 
-    <androidx.slice.widget.SliceView
-        android:id="@+id/slice_view"
+    <ViewFlipper
+        android:id="@+id/viewFlipper"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/homepage_card_vertical_margin"
-        android:layout_marginTop="@dimen/homepage_card_vertical_margin"
-        android:paddingStart="@dimen/homepage_card_padding_start"
-        android:paddingEnd="@dimen/homepage_card_padding_end"/>
+        android:layout_height="wrap_content">
 
-</androidx.cardview.widget.CardView>
+        <androidx.slice.widget.SliceView
+            android:id="@+id/slice_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/homepage_card_vertical_margin"
+            android:layout_marginTop="@dimen/homepage_card_vertical_margin"
+            android:paddingStart="@dimen/homepage_card_padding_start"
+            android:paddingEnd="@dimen/homepage_card_padding_end"/>
+
+        <!--dismissal view-->
+        <LinearLayout
+            android:id="@+id/dismissal_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/homepage_card_padding_start"
+                android:layout_marginTop="@dimen/homepage_card_padding_start"
+                android:text="@string/contextual_card_dismiss_confirm_message"
+                style="@style/TextAppearance.ContextualCardDismissalText"/>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="bottom|end">
+
+                <Button
+                    android:id="@+id/keep"
+                    style="@style/ContextualCardDismissalButton"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/contextual_card_dismiss_keep"/>
+
+                <Button
+                    android:id="@+id/remove"
+                    style="@style/ContextualCardDismissalButton"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/contextual_card_dismiss_remove"/>
+
+            </LinearLayout>
+        </LinearLayout>
+    </ViewFlipper>
+</androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index c3faaba..4bd86dc 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -215,11 +215,11 @@
     <string-array name="wifi_security">
         <!-- The Wi-Fi network does not have any security. -->
         <item>@string/wifi_security_none</item>
-        <item translatable="false">@string/wifi_security_owe</item>
         <item translatable="false">@string/wifi_security_wep</item>
         <item translatable="false">@string/wifi_security_psk_generic</item>
-        <item translatable="false">@string/wifi_security_sae</item>
         <item translatable="false">@string/wifi_security_eap</item>
+        <item translatable="false">@string/wifi_security_owe</item>
+        <item translatable="false">@string/wifi_security_sae</item>
         <item translatable="false">@string/wifi_security_eap_suiteb</item>
     </string-array>
 
diff --git a/res/values/config.xml b/res/values/config.xml
index a9fe35d..d487f46 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -145,4 +145,9 @@
 
     <!-- Whether or not TopLevelSettings should force rounded icon for injected tiles -->
     <bool name="config_force_rounded_icon_TopLevelSettings">true</bool>
+
+    <!-- Settings intelligence package name -->
+    <string name="config_settingsintelligence_package_name" translatable="false">
+        com.android.settings.intelligence
+    </string>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c7a08c0..3a63716 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -10291,4 +10291,10 @@
     <!-- Summary for the top level Privacy Settings [CHAR LIMIT=NONE]-->
     <string name="privacy_dashboard_summary">Permission, permission usage</string>
 
-</resources>
+    <!-- Label for button in contextual card for users to remove the card [CHAR LIMIT=30] -->
+    <string name="contextual_card_dismiss_remove">Remove</string>
+    <!-- Label for button in contextual card for users to keep the card [CHAR LIMIT=30] -->
+    <string name="contextual_card_dismiss_keep">Keep</string>
+    <!-- String for contextual card dismissal [CHAR LIMIT=NONE] -->
+    <string name="contextual_card_dismiss_confirm_message">Remove this suggestion?</string>
+</resources>
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
index aa34123..8fa357b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -16,7 +16,8 @@
 
 <resources>
 
-    <style name="Theme.ActionBar" parent="@android:style/Widget.DeviceDefault.Light.ActionBar.Solid">
+    <style name="Theme.ActionBar"
+           parent="@android:style/Widget.DeviceDefault.Light.ActionBar.Solid">
         <item name="android:contentInsetStart">@dimen/actionbar_contentInsetStart</item>
     </style>
 
@@ -78,8 +79,11 @@
         <item name="android:scrollbarStyle">outsideOverlay</item>
     </style>
 
-    <style name="TrimmedHorizontalProgressBar" parent="android:Widget.Material.ProgressBar.Horizontal">
-        <item name="android:indeterminateDrawable">@drawable/progress_indeterminate_horizontal_material_trimmed</item>
+    <style name="TrimmedHorizontalProgressBar"
+           parent="android:Widget.Material.ProgressBar.Horizontal">
+        <item name="android:indeterminateDrawable">
+            @drawable/progress_indeterminate_horizontal_material_trimmed
+        </item>
         <item name="android:minHeight">3dip</item>
         <item name="android:maxHeight">3dip</item>
     </style>
@@ -162,7 +166,8 @@
         <item name="android:layout_height">wrap_content</item>
     </style>
 
-    <style name="ConfirmDeviceCredentialsAnimationStyle" parent="@*android:style/Animation.Material.Activity">
+    <style name="ConfirmDeviceCredentialsAnimationStyle"
+           parent="@*android:style/Animation.Material.Activity">
         <item name="android:activityOpenEnterAnimation">@anim/confirm_credential_open_enter</item>
         <item name="android:activityOpenExitAnimation">@anim/confirm_credential_open_exit</item>
     </style>
@@ -171,14 +176,16 @@
         <item name="android:background">#ff000000</item>
     </style>
 
-    <style name="SecurityPreferenceButtonContainer" parent="@android:style/Widget.Material.Light.SegmentedButton">
+    <style name="SecurityPreferenceButtonContainer"
+           parent="@android:style/Widget.Material.Light.SegmentedButton">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:weightSum">2</item>
         <item name="android:dividerPadding">8dip</item>
     </style>
 
-    <style name="SecurityPreferenceButton" parent="@android:style/Widget.Material.Light.Button.Borderless">
+    <style name="SecurityPreferenceButton"
+           parent="@android:style/Widget.Material.Light.Button.Borderless">
         <item name="android:layout_width">0dip</item>
         <item name="android:layout_weight">1</item>
         <item name="android:layout_height">wrap_content</item>
@@ -192,7 +199,7 @@
         <item name="android:layout_marginStart">-16dp</item>
     </style>
 
-    <style name="SetupWizardButton.Positive" parent="@style/SuwGlifButton.Primary" />
+    <style name="SetupWizardButton.Positive" parent="@style/SuwGlifButton.Primary"/>
 
     <style name="AccentColorHighlightBorderlessButton">
         <item name="android:colorControlHighlight">?android:attr/colorAccent</item>
@@ -252,17 +259,18 @@
         <item name="android:imeOptions">flagForceAscii|actionDone</item>
     </style>
 
-    <style name="TextAppearance.Medium" parent="@android:style/TextAppearance.Material.Medium" />
-    <style name="TextAppearance.Small" parent="@android:style/TextAppearance.Material.Small" />
+    <style name="TextAppearance.Medium" parent="@android:style/TextAppearance.Material.Medium"/>
+    <style name="TextAppearance.Small" parent="@android:style/TextAppearance.Material.Small"/>
     <style name="TextAppearance.Switch" parent="@android:style/TextAppearance.Material.Title">
         <item name="android:textSize">18sp</item>
     </style>
 
-    <style name="TextAppearance.CategoryTitle" parent="@android:style/TextAppearance.Material.Body2">
+    <style name="TextAppearance.CategoryTitle"
+           parent="@android:style/TextAppearance.Material.Body2">
         <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
-    <style name="TextAppearance.TileTitle" parent="@android:style/TextAppearance.Material.Subhead" />
+    <style name="TextAppearance.TileTitle" parent="@android:style/TextAppearance.Material.Subhead"/>
 
     <style name="TextAppearance.SuggestionTitle"
            parent="@android:style/TextAppearance.Material.Subhead">
@@ -293,12 +301,14 @@
         <item name="android:textStyle">normal</item>
     </style>
 
-    <style name="TextAppearance.RemoveDialogContent" parent="@android:style/TextAppearance.Material">
+    <style name="TextAppearance.RemoveDialogContent"
+           parent="@android:style/TextAppearance.Material">
         <item name="android:textSize">16sp</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
-    <style name="TextAppearance.SearchBar" parent="@android:style/TextAppearance.Material.Widget.Toolbar.Subtitle">
+    <style name="TextAppearance.SearchBar"
+           parent="@android:style/TextAppearance.Material.Widget.Toolbar.Subtitle">
         <item name="android:textSize">@dimen/search_bar_text_size</item>
     </style>
 
@@ -345,6 +355,12 @@
         <item name="android:padding">8dp</item>
     </style>
 
+    <style name="TextAppearance.ContextualCardDismissalText"
+           parent="@android:style/TextAppearance.Material.Body1">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textSize">16sp</item>
+    </style>
+
     <style name="SuggestionCardText">
         <item name="android:textAlignment">viewStart</item>
     </style>
@@ -391,7 +407,8 @@
         <item name="android:lineSpacingMultiplier">1.2</item>
     </style>
 
-    <style name="RingProgressBarStyle" parent="android:style/Widget.Material.ProgressBar.Horizontal">
+    <style name="RingProgressBarStyle"
+           parent="android:style/Widget.Material.ProgressBar.Horizontal">
         <item name="android:indeterminate">false</item>
         <item name="android:max">10000</item>
         <item name="android:mirrorForRtl">false</item>
@@ -470,7 +487,6 @@
         <item name="android:textColor">?android:attr/colorAccent</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textAllCaps">false</item>
-        <item name="android:fontFamily">sans-serif-medium</item>
     </style>
 
     <style name="ConditionHalfCardBorderlessButton"
@@ -483,4 +499,9 @@
         <item name="android:textAlignment">viewEnd</item>
     </style>
 
+    <style name="ContextualCardDismissalButton"
+           parent="android:Widget.DeviceDefault.Button.Borderless.Colored">
+        <item name="android:textAllCaps">false</item>
+    </style>
+
 </resources>
diff --git a/src/com/android/settings/deviceinfo/StorageSettings.java b/src/com/android/settings/deviceinfo/StorageSettings.java
index 49d5455..b32da9d 100644
--- a/src/com/android/settings/deviceinfo/StorageSettings.java
+++ b/src/com/android/settings/deviceinfo/StorageSettings.java
@@ -146,6 +146,7 @@
         switch (vol.getType()) {
             case VolumeInfo.TYPE_PRIVATE:
             case VolumeInfo.TYPE_PUBLIC:
+            case VolumeInfo.TYPE_STUB:
                 return true;
             default:
                 return false;
@@ -178,7 +179,8 @@
                 final int color = COLOR_PRIVATE[privateCount++ % COLOR_PRIVATE.length];
                 mInternalCategory.addPreference(
                         new StorageVolumePreference(context, vol, color, volumeTotalBytes));
-            } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
+            } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC
+                    || vol.getType() == VolumeInfo.TYPE_STUB) {
                 mExternalCategory.addPreference(
                         new StorageVolumePreference(context, vol, COLOR_PUBLIC, 0));
             }
@@ -306,6 +308,8 @@
 
             } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
                 return handlePublicVolumeClick(getContext(), vol);
+            } else if (vol.getType() == VolumeInfo.TYPE_STUB) {
+                return handleStubVolumeClick(getContext(), vol);
             }
 
         } else if (key.startsWith("disk:")) {
@@ -330,6 +334,16 @@
     }
 
     @VisibleForTesting
+    static boolean handleStubVolumeClick(Context context, VolumeInfo vol) {
+        final Intent intent = vol.buildBrowseIntent();
+        if (vol.isMountedReadable() && intent != null) {
+            context.startActivity(intent);
+            return true;
+        }
+        return false;
+    }
+
+    @VisibleForTesting
     static boolean handlePublicVolumeClick(Context context, VolumeInfo vol) {
         final Intent intent = vol.buildBrowseIntent();
         if (vol.isMountedReadable() && intent != null) {
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardController.java b/src/com/android/settings/homepage/contextualcards/ContextualCardController.java
index 95669ce..4d31e79 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardController.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardController.java
@@ -28,5 +28,7 @@
 
     void onActionClick(ContextualCard card);
 
+    void onDismissed(ContextualCard card);
+
     void setCardUpdateListener(ContextualCardUpdateListener listener);
 }
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
index 39ceff3..db05abb 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
@@ -146,14 +146,14 @@
         onContextualCardUpdated(cards.stream().collect(groupingBy(ContextualCard::getCardType)));
     }
 
-    void setListener(ContextualCardUpdateListener listener) {
-        mListener = listener;
-    }
-
     public ControllerRendererPool getControllerRendererPool() {
         return mControllerRendererPool;
     }
 
+    void setListener(ContextualCardUpdateListener listener) {
+        mListener = listener;
+    }
+
     static class CardContentLoaderCallbacks implements
             LoaderManager.LoaderCallbacks<List<ContextualCard>> {
 
diff --git a/src/com/android/settings/homepage/contextualcards/ControllerRendererPool.java b/src/com/android/settings/homepage/contextualcards/ControllerRendererPool.java
index 7d9d5a8..723b344 100644
--- a/src/com/android/settings/homepage/contextualcards/ControllerRendererPool.java
+++ b/src/com/android/settings/homepage/contextualcards/ControllerRendererPool.java
@@ -126,7 +126,8 @@
         if (ConditionContextualCardRenderer.class == clz) {
             return new ConditionContextualCardRenderer(context, this /* controllerRendererPool */);
         } else if (SliceContextualCardRenderer.class == clz) {
-            return new SliceContextualCardRenderer(context, lifecycleOwner);
+            return new SliceContextualCardRenderer(context, lifecycleOwner,
+                    this /* controllerRendererPool */);
         } else if (LegacySuggestionContextualCardRenderer.class == clz) {
             return new LegacySuggestionContextualCardRenderer(context,
                     this /* controllerRendererPool */);
diff --git a/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java b/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java
index 10881d9..84ceabd 100644
--- a/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java
+++ b/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java
@@ -77,6 +77,11 @@
 
     @Override
     public void onActionClick(ContextualCard contextualCard) {
+
+    }
+
+    @Override
+    public void onDismissed(ContextualCard contextualCard) {
         final ConditionalContextualCard card = (ConditionalContextualCard) contextualCard;
         mConditionManager.onActionClick(card.getConditionId());
     }
diff --git a/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java
index 5e4e749..3405dd2 100644
--- a/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java
+++ b/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java
@@ -112,8 +112,8 @@
                 metricsFeatureProvider.action(
                         viewContext, MetricsProto.MetricsEvent.ACTION_SETTINGS_CONDITION_BUTTON,
                         card.getMetricsConstant());
-                mControllerRendererPool.getController(mContext, card.getCardType()).onActionClick(
-                        card);
+                mControllerRendererPool.getController(mContext, card.getCardType())
+                        .onDismissed(card);
             });
         } else {
             button.setVisibility(View.GONE);
diff --git a/src/com/android/settings/homepage/contextualcards/legacysuggestion/LegacySuggestionContextualCardController.java b/src/com/android/settings/homepage/contextualcards/legacysuggestion/LegacySuggestionContextualCardController.java
index 550f845..7fa004a 100644
--- a/src/com/android/settings/homepage/contextualcards/legacysuggestion/LegacySuggestionContextualCardController.java
+++ b/src/com/android/settings/homepage/contextualcards/legacysuggestion/LegacySuggestionContextualCardController.java
@@ -87,6 +87,11 @@
     }
 
     @Override
+    public void onDismissed(ContextualCard card) {
+
+    }
+
+    @Override
     public void setCardUpdateListener(ContextualCardUpdateListener listener) {
         mCardUpdateListener = listener;
     }
diff --git a/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java b/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java
new file mode 100644
index 0000000..ff26888
--- /dev/null
+++ b/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2018 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.homepage.contextualcards.slices;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.slices.SliceBackgroundWorker;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+public class BluetoothUpdateWorker extends SliceBackgroundWorker implements BluetoothCallback {
+
+    private static final String TAG = "BluetoothUpdateWorker";
+
+    private final Context mContext;
+    private final Uri mUri;
+    private final LocalBluetoothManager mLocalBluetoothManager;
+
+    public BluetoothUpdateWorker(Context context, Uri uri) {
+        super(context, uri);
+
+        mContext = context;
+        mUri = uri;
+        mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
+    }
+
+    @Override
+    protected void onSlicePinned() {
+        if (mLocalBluetoothManager == null) {
+            Log.i(TAG, "onSlicePinned() Bluetooth is unsupported.");
+            return;
+        }
+        mLocalBluetoothManager.getEventManager().registerCallback(this);
+    }
+
+    @Override
+    protected void onSliceUnpinned() {
+        if (mLocalBluetoothManager == null) {
+            Log.i(TAG, "onSliceUnpinned() Bluetooth is unsupported.");
+            return;
+        }
+        mLocalBluetoothManager.getEventManager().unregisterCallback(this);
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+        notifySliceChange();
+    }
+
+    @Override
+    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+        notifySliceChange();
+    }
+
+    @Override
+    public void onBluetoothStateChanged(int bluetoothState) {
+        notifySliceChange();
+    }
+
+    @Override
+    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+        notifySliceChange();
+    }
+
+    @Override
+    public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
+            int bluetoothProfile) {
+        notifySliceChange();
+    }
+
+    private void notifySliceChange() {
+        mContext.getContentResolver().notifyChange(mUri, null);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSlice.java b/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSlice.java
index 54cd82f..ef7c1bd 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSlice.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSlice.java
@@ -18,7 +18,6 @@
 
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,7 +27,6 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
-import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 
@@ -55,10 +53,9 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * TODO(b/114807655): Contextual Home Page - Connected Device
@@ -66,11 +63,7 @@
  * Show connected device info if one is currently connected. UI for connected device should
  * match Connected Devices > Currently Connected Devices
  *
- * This Slice will show multiple currently connected devices, which includes:
- * 1) Bluetooth.
- * 2) Docks.
- * ...
- * TODO Other device types are under checking to support, will update later.
+ * TODO This class will be refactor for Bluetooth connected devices only.
  */
 public class ConnectedDeviceSlice implements CustomSliceable {
 
@@ -138,7 +131,6 @@
                         .setAccentColor(Utils.getColorAccentDefaultColor(mContext));
 
         // Get row builders by connected devices, e.g. Bluetooth.
-        // TODO Add other type connected devices, e.g. Docks.
         final List<ListBuilder.RowBuilder> rows = getBluetoothRowBuilder(primarySliceAction);
 
         // Return a header with IsError flag, if no connected devices.
@@ -181,13 +173,18 @@
     public void onNotifyChange(Intent intent) {
     }
 
+    @Override
+    public Class getBackgroundWorkerClass() {
+        return BluetoothUpdateWorker.class;
+    }
+
     @VisibleForTesting
     List<CachedBluetoothDevice> getBluetoothConnectedDevices() {
         final List<CachedBluetoothDevice> connectedBluetoothList = new ArrayList<>();
 
         // If Bluetooth is disable, skip to get the bluetooth devices.
         if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
-            Log.d(TAG, "Cannot get Bluetooth connected devices, Bluetooth is disabled.");
+            Log.i(TAG, "Cannot get Bluetooth connected devices, Bluetooth is disabled.");
             return connectedBluetoothList;
         }
 
@@ -195,25 +192,15 @@
         final LocalBluetoothManager bluetoothManager =
                 com.android.settings.bluetooth.Utils.getLocalBtManager(mContext);
         if (bluetoothManager == null) {
-            Log.d(TAG, "Cannot get Bluetooth connected devices, Bluetooth is not supported.");
+            Log.i(TAG, "Cannot get Bluetooth connected devices, Bluetooth is unsupported.");
             return connectedBluetoothList;
         }
         final Collection<CachedBluetoothDevice> cachedDevices =
                 bluetoothManager.getCachedDeviceManager().getCachedDevicesCopy();
 
-        // Get all connected Bluetooth devices and use Map to filter duplicated Bluetooth.
-        final Map<BluetoothDevice, CachedBluetoothDevice> connectedBluetoothMap = new ArrayMap<>();
-        for (CachedBluetoothDevice device : cachedDevices) {
-            if (device.isConnected() && !connectedBluetoothMap.containsKey(device.getDevice())) {
-                connectedBluetoothMap.put(device.getDevice(), device);
-            }
-        }
-
-        // Sort connected Bluetooth devices.
-        connectedBluetoothList.addAll(connectedBluetoothMap.values());
-        Collections.sort(connectedBluetoothList, COMPARATOR);
-
-        return connectedBluetoothList;
+        // Get connected Bluetooth devices and sort them.
+        return cachedDevices.stream().filter(device -> device.isConnected()).sorted(
+                COMPARATOR).collect(Collectors.toList());
     }
 
     @VisibleForTesting
diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java
index 5711dc5..6ab8f40 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java
@@ -25,6 +25,10 @@
  */
 public class SliceContextualCardController implements ContextualCardController {
 
+    private static final String TAG = "SliceCardController";
+
+    private ContextualCardUpdateListener mCardUpdateListener;
+
     @Override
     public int getCardType() {
         return ContextualCard.CardType.SLICE;
@@ -37,11 +41,16 @@
 
     @Override
     public void onActionClick(ContextualCard card) {
+        //TODO(b/113783548): Implement feedback mechanism
+    }
 
+    @Override
+    public void onDismissed(ContextualCard card) {
+        //TODO(b/113783548): Mark this card as dismissed in db and reload loader.
     }
 
     @Override
     public void setCardUpdateListener(ContextualCardUpdateListener listener) {
-
+            mCardUpdateListener = listener;
     }
 }
diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java
index 5fc4473..74f25eb 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java
@@ -22,6 +22,8 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.View;
+import android.widget.Button;
+import android.widget.ViewFlipper;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -37,6 +39,7 @@
 import com.android.settings.R;
 import com.android.settings.homepage.contextualcards.ContextualCard;
 import com.android.settings.homepage.contextualcards.ContextualCardRenderer;
+import com.android.settings.homepage.contextualcards.ControllerRendererPool;
 
 import java.util.Map;
 
@@ -54,11 +57,14 @@
 
     private final Context mContext;
     private final LifecycleOwner mLifecycleOwner;
+    private final ControllerRendererPool mControllerRendererPool;
 
-    public SliceContextualCardRenderer(Context context, LifecycleOwner lifecycleOwner) {
+    public SliceContextualCardRenderer(Context context, LifecycleOwner lifecycleOwner,
+            ControllerRendererPool controllerRendererPool) {
         mContext = context;
         mLifecycleOwner = lifecycleOwner;
         mSliceLiveDataMap = new ArrayMap<>();
+        mControllerRendererPool = controllerRendererPool;
     }
 
     @Override
@@ -104,6 +110,27 @@
 
         // Set this listener so we can log the interaction users make on the slice
         cardHolder.sliceView.setOnSliceActionListener(this);
+
+        initDismissalActions(cardHolder, card);
+    }
+
+    private void initDismissalActions(SliceViewHolder cardHolder, ContextualCard card) {
+        final ViewFlipper viewFlipper = cardHolder.itemView.findViewById(R.id.viewFlipper);
+        cardHolder.sliceView.setOnLongClickListener(v -> {
+            viewFlipper.showNext();
+            return true;
+        });
+
+        final Button btnKeep = cardHolder.itemView.findViewById(R.id.keep);
+        btnKeep.setOnClickListener(v -> {
+            viewFlipper.showPrevious();
+        });
+
+        final Button btnRemove = cardHolder.itemView.findViewById(R.id.remove);
+        btnRemove.setOnClickListener(v -> {
+            mControllerRendererPool.getController(mContext, card.getCardType()).onDismissed(
+                    card);
+        });
     }
 
     @Override
diff --git a/src/com/android/settings/search/SearchFeatureProvider.java b/src/com/android/settings/search/SearchFeatureProvider.java
index 9c1f07c..464b8f5 100644
--- a/src/com/android/settings/search/SearchFeatureProvider.java
+++ b/src/com/android/settings/search/SearchFeatureProvider.java
@@ -29,6 +29,7 @@
 import android.widget.Toolbar;
 
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.search.SearchIndexableResources;
@@ -55,8 +56,8 @@
      */
     SearchIndexableResources getSearchIndexableResources();
 
-    default String getSettingsIntelligencePkgName() {
-        return "com.android.settings.intelligence";
+    default String getSettingsIntelligencePkgName(Context context) {
+        return context.getString(R.string.config_settingsintelligence_package_name);
     }
 
     /**
@@ -66,7 +67,7 @@
         if (activity == null || toolbar == null) {
             return;
         }
-        if (!Utils.isPackageEnabled(activity, getSettingsIntelligencePkgName())) {
+        if (!Utils.isPackageEnabled(activity, getSettingsIntelligencePkgName(activity))) {
             final ViewGroup parent = (ViewGroup)toolbar.getParent();
             if (parent != null) {
                 parent.setVisibility(View.GONE);
@@ -84,7 +85,7 @@
 
         toolbar.setOnClickListener(tb -> {
             final Intent intent = SEARCH_UI_INTENT;
-            intent.setPackage(getSettingsIntelligencePkgName());
+            intent.setPackage(getSettingsIntelligencePkgName(activity));
             final Context context = activity.getApplicationContext();
 
             FeatureFactory.getFactory(context).getSlicesFeatureProvider()
diff --git a/src/com/android/settings/search/SearchFeatureProviderImpl.java b/src/com/android/settings/search/SearchFeatureProviderImpl.java
index 4f2f8db..a5a8777 100644
--- a/src/com/android/settings/search/SearchFeatureProviderImpl.java
+++ b/src/com/android/settings/search/SearchFeatureProviderImpl.java
@@ -41,7 +41,7 @@
         }
         final String packageName = caller.getPackageName();
         final boolean isSettingsPackage = TextUtils.equals(packageName, context.getPackageName())
-                || TextUtils.equals(getSettingsIntelligencePkgName(), packageName);
+                || TextUtils.equals(getSettingsIntelligencePkgName(context), packageName);
         final boolean isWhitelistedPackage =
                 isSignatureWhitelisted(context, caller.getPackageName());
         if (isSettingsPackage || isWhitelistedPackage) {
diff --git a/src/com/android/settings/search/actionbar/SearchMenuController.java b/src/com/android/settings/search/actionbar/SearchMenuController.java
index 0caa308..22adbeb 100644
--- a/src/com/android/settings/search/actionbar/SearchMenuController.java
+++ b/src/com/android/settings/search/actionbar/SearchMenuController.java
@@ -57,8 +57,8 @@
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         final Context context = mHost.getContext();
-        final String SettingsIntelligencePkgName = FeatureFactory.getFactory(context)
-                .getSearchFeatureProvider().getSettingsIntelligencePkgName();
+        final String SettingsIntelligencePkgName = context.getString(
+                R.string.config_settingsintelligence_package_name);
         if (!Utils.isDeviceProvisioned(mHost.getContext())) {
             return;
         }
diff --git a/src/com/android/settings/wifi/qrcode/QrCamera.java b/src/com/android/settings/wifi/qrcode/QrCamera.java
new file mode 100644
index 0000000..3230035
--- /dev/null
+++ b/src/com/android/settings/wifi/qrcode/QrCamera.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settings.wifi.qrcode;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PreviewCallback;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.WindowManager;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.ReaderException;
+import com.google.zxing.Result;
+import com.google.zxing.common.HybridBinarizer;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * Manage the camera for the QR scanner and help the decoder to get the image inside the scanning
+ * frame. Caller prepares a {@link SurfaceHolder} then call {@link #start(SurfaceHolder)} to
+ * start QR Code scanning. The scanning result will return by ScannerCallback interface. Caller
+ * can also call {@link #stop()} to halt QR Code scanning before the result returned.
+ */
+public class QrCamera extends Handler {
+    private static final String TAG = "QrCamera";
+
+    private static final int MSG_AUTO_FOCUS = 1;
+
+    private static double MIN_RATIO_DIFF_PERCENT = 0.1;
+    private static long AUTOFOCUS_INTERVAL_MS = 1500L;
+
+    private static Map<DecodeHintType, List<BarcodeFormat>> HINTS = new ArrayMap<>();
+    private static List<BarcodeFormat> FORMATS = new ArrayList<>();
+
+    static {
+        FORMATS.add(BarcodeFormat.QR_CODE);
+        HINTS.put(DecodeHintType.POSSIBLE_FORMATS, FORMATS);
+    }
+
+    private Camera mCamera;
+    private Size mPreviewSize;
+    private WeakReference<Context> mContext;
+    private ScannerCallback mScannerCallback;
+    private MultiFormatReader mReader;
+    private DecodingTask mDecodeTask;
+    private int mCameraOrientation;
+    private Camera.Parameters mParameters;
+
+    public QrCamera(Context context, ScannerCallback callback) {
+        mContext =  new WeakReference<Context>(context);
+        mScannerCallback = callback;
+        mReader = new MultiFormatReader();
+        mReader.setHints(HINTS);
+    }
+
+    void start(SurfaceHolder surfaceHolder) {
+        if (mDecodeTask == null) {
+            mDecodeTask = new DecodingTask(surfaceHolder);
+            mDecodeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+        }
+    }
+
+    void stop() {
+        removeMessages(MSG_AUTO_FOCUS);
+        if (mDecodeTask != null) {
+            mDecodeTask.cancel(true);
+            mDecodeTask = null;
+        }
+        if (mCamera != null) {
+            mCamera.stopPreview();
+        }
+    }
+
+    /** The scanner which includes this QrCamera class should implement this */
+    interface ScannerCallback {
+
+        /**
+         * The function used to handle the decoding result of the QR code.
+         *
+         * @param result the result QR code after decoding.
+         */
+        void handleSuccessfulResult(String result);
+
+        /** Request the QR code scanner to handle the failure happened. */
+        void handleCameraFailure();
+
+        /**
+         * The function used to get the background View size.
+         *
+         * @return Includes the background view size.
+         */
+        Size getViewSize();
+
+        /**
+         * The function used to get the frame position inside the view
+         *
+         * @param previewSize Is the preview size set by camera
+         * @param cameraOrientation Is the orientation of current Camera
+         * @return The rectangle would like to crop from the camera preview shot.
+         */
+        Rect getFramePosition(Size previewSize, int cameraOrientation);
+    }
+
+    private void setCameraParameter() {
+        mParameters = mCamera.getParameters();
+        mPreviewSize = getBestPreviewSize(mParameters);
+        mParameters.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
+        mParameters.setPictureSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
+
+        if (mParameters.getSupportedFlashModes().contains(Parameters.FLASH_MODE_OFF)) {
+            mParameters.setFlashMode(Parameters.FLASH_MODE_OFF);
+        }
+
+        final List<String> supportedFocusModes = mParameters.getSupportedFocusModes();
+        if (supportedFocusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+            mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
+        } else if (supportedFocusModes.contains(Parameters.FOCUS_MODE_AUTO)) {
+            mParameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
+        }
+        mCamera.setParameters(mParameters);
+    }
+
+    private boolean startPreview() {
+        if (mContext.get() == null) {
+            return false;
+        }
+
+        final WindowManager winManager =
+                (WindowManager) mContext.get().getSystemService(Context.WINDOW_SERVICE);
+        final int rotation = winManager.getDefaultDisplay().getRotation();
+        int degrees = 0;
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                degrees = 0;
+                break;
+            case Surface.ROTATION_90:
+                degrees = 90;
+                break;
+            case Surface.ROTATION_180:
+                degrees = 180;
+                break;
+            case Surface.ROTATION_270:
+                degrees = 270;
+                break;
+        }
+        final int rotateDegrees = (mCameraOrientation - degrees + 360) % 360;
+        mCamera.setDisplayOrientation(rotateDegrees);
+        mCamera.startPreview();
+        if (mParameters.getFocusMode() == Parameters.FOCUS_MODE_AUTO) {
+            mCamera.autoFocus(/* Camera.AutoFocusCallback */ null);
+            sendMessageDelayed(obtainMessage(MSG_AUTO_FOCUS), AUTOFOCUS_INTERVAL_MS);
+        }
+        return true;
+    }
+
+    private class DecodingTask extends AsyncTask<Void, Void, String> {
+        private QrYuvLuminanceSource mImage;
+        private SurfaceHolder mSurfaceHolder;
+
+        private DecodingTask(SurfaceHolder surfaceHolder) {
+            mSurfaceHolder = surfaceHolder;
+        }
+
+        @Override
+        protected String doInBackground(Void... tmp) {
+            if (!initCamera(mSurfaceHolder)) {
+                return null;
+            }
+
+            final Semaphore imageGot = new Semaphore(0);
+            while (true) {
+                // This loop will try to capture preview image continuously until a valid QR Code
+                // decoded. The caller can also call {@link #stop()} to inturrupts scanning loop.
+                mCamera.setOneShotPreviewCallback(
+                        (imageData, camera) -> {
+                            mImage = getFrameImage(imageData);
+                            imageGot.release();
+                        });
+                try {
+                    // Semaphore.acquire() blocking until permit is available, or the thread is
+                    // interrupted.
+                    imageGot.acquire();
+                    Result qrCode = null;
+                    try {
+                        qrCode =
+                                mReader.decodeWithState(
+                                        new BinaryBitmap(new HybridBinarizer(mImage)));
+                    } catch (ReaderException e) {
+                        // No logging since every time the reader cannot decode the
+                        // image, this ReaderException will be thrown.
+                    } finally {
+                        mReader.reset();
+                    }
+                    if (qrCode != null) {
+                        return qrCode.getText();
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    return null;
+                }
+            }
+        }
+
+        @Override
+        protected void onPostExecute(String qrCode) {
+            if (qrCode != null) {
+                mScannerCallback.handleSuccessfulResult(qrCode);
+            }
+        }
+
+        private boolean initCamera(SurfaceHolder surfaceHolder) {
+            final int numberOfCameras = Camera.getNumberOfCameras();
+            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+            try {
+                for (int i = 0; i < numberOfCameras; ++i) {
+                    Camera.getCameraInfo(i, cameraInfo);
+                    if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
+                        mCamera = Camera.open(i);
+                        mCamera.setPreviewDisplay(surfaceHolder);
+                        mCameraOrientation = cameraInfo.orientation;
+                        break;
+                    }
+                }
+                if (mCamera == null) {
+                    Log.e(TAG, "Cannot find available back camera.");
+                    mScannerCallback.handleCameraFailure();
+                    return false;
+                }
+                setCameraParameter();
+                if (!startPreview()) {
+                    Log.e(TAG, "Error to init Camera");
+                    mCamera = null;
+                    mScannerCallback.handleCameraFailure();
+                    return false;
+                }
+                return true;
+            } catch (IOException e) {
+                Log.e(TAG, "Error to init Camera");
+                mCamera = null;
+                mScannerCallback.handleCameraFailure();
+                return false;
+            }
+        }
+    }
+
+    private QrYuvLuminanceSource getFrameImage(byte[] imageData) {
+        final Rect frame = mScannerCallback.getFramePosition(mPreviewSize, mCameraOrientation);
+        final Camera.Size size = mParameters.getPictureSize();
+        QrYuvLuminanceSource image = new QrYuvLuminanceSource(imageData, size.width, size.height);
+        return (QrYuvLuminanceSource)
+                image.crop(frame.left, frame.top, frame.width(), frame.height());
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_AUTO_FOCUS:
+                // Calling autoFocus(null) will only trigger the camera to focus once. In order
+                // to make the camera continuously auto focus during scanning, need to periodly
+                // trigger it.
+                mCamera.autoFocus(/* Camera.AutoFocusCallback */ null);
+                sendMessageDelayed(obtainMessage(MSG_AUTO_FOCUS), AUTOFOCUS_INTERVAL_MS);
+                break;
+            default:
+                Log.d(TAG, "Unexpected Message: " + msg.what);
+        }
+    }
+
+    private Size getBestPreviewSize(Camera.Parameters parameters) {
+        final Size windowSize = mScannerCallback.getViewSize();
+        Size bestChoice = new Size(0, 0);
+        for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
+            if (size.width <= windowSize.getWidth() && size.height <= windowSize.getHeight()) {
+                bestChoice = new Size(size.width, size.height);
+                break;
+            }
+        }
+        return bestChoice;
+    }
+
+    @VisibleForTesting
+    protected void decodeImage(BinaryBitmap image) {
+        Result qrCode = null;
+
+        try {
+            qrCode = mReader.decodeWithState(image);
+        } catch (ReaderException e) {
+        } finally {
+            mReader.reset();
+        }
+
+        if (qrCode != null) {
+            mScannerCallback.handleSuccessfulResult(qrCode.getText());
+        }
+    }
+}
diff --git a/src/com/android/settings/wifi/qrcode/QrYuvLuminanceSource.java b/src/com/android/settings/wifi/qrcode/QrYuvLuminanceSource.java
new file mode 100644
index 0000000..874a758
--- /dev/null
+++ b/src/com/android/settings/wifi/qrcode/QrYuvLuminanceSource.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settings.wifi.qrcode;
+
+import com.google.zxing.LuminanceSource;
+
+/**
+ * This helper class implements crop method to crop preview picture.
+ */
+public class QrYuvLuminanceSource extends LuminanceSource {
+
+    private byte[] mYuvData;
+    private int mWidth;
+    private int mHeight;
+
+    public QrYuvLuminanceSource(byte[] yuvData, int width, int height) {
+        super(width, height);
+
+        mWidth = width;
+        mHeight = height;
+        mYuvData = yuvData;
+    }
+
+    @Override
+    public boolean isCropSupported() {
+        return true;
+    }
+
+    @Override
+    public LuminanceSource crop(int left, int top, int crop_width, int crop_height) {
+        final byte[] newImage = new byte[crop_width * crop_height];
+        int inputOffset = top * mWidth + left;
+
+        if (left + crop_width > mWidth || top + crop_height > mHeight) {
+            throw new IllegalArgumentException("cropped rectangle does not fit within image data.");
+        }
+
+        for (int y = 0; y < crop_height; y++) {
+            System.arraycopy(mYuvData, inputOffset, newImage, y * crop_width, crop_width);
+            inputOffset += mWidth;
+        }
+        return new QrYuvLuminanceSource(newImage, crop_width, crop_height);
+    }
+
+    @Override
+    public byte[] getRow(int y, byte[] row) {
+        if (y < 0 || y >= mHeight) {
+            throw new IllegalArgumentException("Requested row is outside the image: " + y);
+        }
+        if (row == null || row.length < mWidth) {
+            row = new byte[mWidth];
+        }
+        System.arraycopy(mYuvData, y * mWidth, row, 0, mWidth);
+        return row;
+    }
+
+    @Override
+    public byte[] getMatrix() {
+        return mYuvData;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/StorageSettingsTest.java b/tests/robotests/src/com/android/settings/deviceinfo/StorageSettingsTest.java
index cb02c76..2598105 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/StorageSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/StorageSettingsTest.java
@@ -66,4 +66,16 @@
         verify(mActivity, never()).startActivity(null);
         verify(mActivity).startActivity(any(Intent.class));
     }
+
+    @Test
+    public void handleStubVolumeClick_startsANonNullActivityWhenVolumeHasNoBrowse() {
+        VolumeInfo volumeInfo = mock(VolumeInfo.class, RETURNS_DEEP_STUBS);
+        when(volumeInfo.isMountedReadable()).thenReturn(true);
+
+        StorageSettings.handleStubVolumeClick(mActivity, volumeInfo);
+
+        verify(mActivity, never()).startActivity(null);
+        verify(mActivity).startActivity(any(Intent.class));
+    }
+
 }
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
new file mode 100644
index 0000000..1bc4f2a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 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.homepage.contextualcards.slices;
+
+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.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class BluetoothUpdateWorkerTest {
+
+    private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
+
+    private BluetoothUpdateWorker mBluetoothUpdateWorker;
+    private ContentResolver mResolver;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = spy(RuntimeEnvironment.application);
+        mBluetoothUpdateWorker = new BluetoothUpdateWorker(mContext, URI);
+        mResolver = mock(ContentResolver.class);
+        doReturn(mResolver).when(mContext).getContentResolver();
+    }
+
+    @Test
+    public void onAclConnectionStateChanged_shouldNotifyChange() {
+        mBluetoothUpdateWorker.onAclConnectionStateChanged(null, 0);
+
+        verify(mResolver).notifyChange(URI, null);
+    }
+
+    @Test
+    public void onActiveDeviceChanged_shouldNotifyChange() {
+        mBluetoothUpdateWorker.onActiveDeviceChanged(null, 0);
+
+        verify(mResolver).notifyChange(URI, null);
+    }
+
+    @Test
+    public void onBluetoothStateChanged_shouldNotifyChange() {
+        mBluetoothUpdateWorker.onBluetoothStateChanged(0);
+
+        verify(mResolver).notifyChange(URI, null);
+    }
+
+    @Test
+    public void onConnectionStateChanged_shouldNotifyChange() {
+        mBluetoothUpdateWorker.onConnectionStateChanged(null, 0);
+
+        verify(mResolver).notifyChange(URI, null);
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_shouldNotifyChange() {
+        mBluetoothUpdateWorker.onProfileConnectionStateChanged(null, 0, 0);
+
+        verify(mResolver).notifyChange(URI, null);
+    }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java
index 7a07d35..1ba329c 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java
@@ -24,15 +24,20 @@
 import android.net.Uri;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.Button;
+import android.widget.ViewFlipper;
 
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LiveData;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.slice.Slice;
+import androidx.slice.widget.SliceView;
 
+import com.android.settings.R;
 import com.android.settings.homepage.contextualcards.ContextualCard;
 import com.android.settings.homepage.contextualcards.ContextualCardsFragment;
+import com.android.settings.homepage.contextualcards.ControllerRendererPool;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Before;
@@ -47,6 +52,10 @@
 
     @Mock
     private LiveData<Slice> mSliceLiveData;
+    @Mock
+    private ControllerRendererPool mControllerRendererPool;
+    @Mock
+    private SliceContextualCardController mController;
 
     private Context mContext;
     private SliceContextualCardRenderer mRenderer;
@@ -57,7 +66,8 @@
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
         mLifecycleOwner = new ContextualCardsFragment();
-        mRenderer = new SliceContextualCardRenderer(mContext, mLifecycleOwner);
+        mRenderer = new SliceContextualCardRenderer(mContext, mLifecycleOwner,
+                mControllerRendererPool);
     }
 
     @Test
@@ -103,7 +113,7 @@
     }
 
     @Test
-    public void bindview_sliceLiveDataShouldRemoveObservers() {
+    public void bindView_sliceLiveDataShouldRemoveObservers() {
         final String sliceUri = "content://com.android.settings.slices/action/flashlight";
         mRenderer.mSliceLiveDataMap.put(sliceUri, mSliceLiveData);
 
@@ -112,14 +122,45 @@
         verify(mSliceLiveData).removeObservers(mLifecycleOwner);
     }
 
+    @Test
+    public void longClick_shouldFlipCard() {
+        final String sliceUri = "content://com.android.settings.slices/action/flashlight";
+        final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
+        final View card = viewHolder.itemView.findViewById(R.id.slice_view);
+        final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.viewFlipper);
+        final View dismissalView = viewHolder.itemView.findViewById(R.id.dismissal_view);
+        mRenderer.bindView(viewHolder, buildContextualCard(sliceUri));
+
+        assertThat(card).isNotNull();
+        card.performLongClick();
+
+        assertThat(viewFlipper.getCurrentView()).isEqualTo(dismissalView);
+    }
+
+    @Test
+    public void viewClick_keepCard_shouldFlipBackToSlice() {
+        final String sliceUri = "content://com.android.settings.slices/action/flashlight";
+        final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
+        final View card = viewHolder.itemView.findViewById(R.id.slice_view);
+        final Button btnKeep = viewHolder.itemView.findViewById(R.id.keep);
+        final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.viewFlipper);
+        mRenderer.bindView(viewHolder, buildContextualCard(sliceUri));
+
+        assertThat(card).isNotNull();
+        card.performLongClick();
+        assertThat(btnKeep).isNotNull();
+        btnKeep.performClick();
+
+        assertThat(viewFlipper.getCurrentView()).isInstanceOf(SliceView.class);
+    }
+
     private RecyclerView.ViewHolder getSliceViewHolder() {
         final int viewType = mRenderer.getViewType(false /* isHalfWidth */);
         final RecyclerView recyclerView = new RecyclerView(mContext);
         recyclerView.setLayoutManager(new LinearLayoutManager(mContext));
         final View view = LayoutInflater.from(mContext).inflate(viewType, recyclerView, false);
-        final RecyclerView.ViewHolder viewHolder = mRenderer.createViewHolder(view);
 
-        return viewHolder;
+        return mRenderer.createViewHolder(view);
     }
 
     private ContextualCard buildContextualCard(String sliceUri) {
diff --git a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
index d0546b6..bcb9372 100644
--- a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
@@ -89,7 +89,7 @@
 
     @Test
     public void verifyLaunchSearchResultPageCaller_settingsIntelligenceCaller_shouldNotCrash() {
-        final String packageName = mProvider.getSettingsIntelligencePkgName();
+        final String packageName = mProvider.getSettingsIntelligencePkgName(mActivity);
         final ComponentName cn = new ComponentName(packageName, "class");
         mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn);
     }
diff --git a/tests/robotests/src/com/android/settings/wifi/qrcode/QrCameraTest.java b/tests/robotests/src/com/android/settings/wifi/qrcode/QrCameraTest.java
new file mode 100644
index 0000000..ca74c19
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/qrcode/QrCameraTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.qrcode;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.util.Size;
+import android.view.SurfaceHolder;
+
+import com.android.settings.R;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.RGBLuminanceSource;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.HybridBinarizer;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class QrCameraTest {
+
+    @Mock
+    private SurfaceHolder mSurfaceHolder;
+
+    private QrCamera mCamera;
+    private Context mContext;
+
+    private String mQrCode;
+    CountDownLatch mCallbackSignal;
+    private boolean mCameraCallbacked;
+
+    private class ScannerTestCallback implements QrCamera.ScannerCallback {
+        @Override
+        public Size getViewSize() {
+            return new Size(0, 0);
+        }
+
+        @Override
+        public Rect getFramePosition(Size previewSize, int cameraOrientation) {
+            return new Rect(0,0,0,0);
+        }
+
+        @Override
+        public void handleSuccessfulResult(String qrCode) {
+            mQrCode = qrCode;
+        }
+
+        @Override
+        public void handleCameraFailure() {
+            mCameraCallbacked = true;
+            mCallbackSignal.countDown();
+        }
+    }
+
+    private ScannerTestCallback mScannerCallback;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mScannerCallback = new ScannerTestCallback();
+        mCamera = new QrCamera(mContext, mScannerCallback);
+        mSurfaceHolder = mock(SurfaceHolder.class);
+        mQrCode = "";
+        mCameraCallbacked = false;
+        mCallbackSignal = null;
+    }
+
+    @Test
+    public void testCamera_Init_Callback() throws InterruptedException {
+        mCallbackSignal = new CountDownLatch(1);
+        mCamera.start(mSurfaceHolder);
+        mCallbackSignal.await(5000, TimeUnit.MILLISECONDS);
+        assertThat(mCameraCallbacked).isTrue();
+    }
+
+    @Test
+    public void testDecode_PictureCaptured_QrCodeCorrectValue() {
+        final String googleUrl = "http://www.google.com";
+
+        try {
+            Bitmap bmp = encodeQrCode(googleUrl, 320);
+            int[] intArray = new int[bmp.getWidth() * bmp.getHeight()];
+            bmp.getPixels(intArray, 0, bmp.getWidth(), 0, 0, bmp.getWidth(), bmp.getHeight());
+            LuminanceSource source = new RGBLuminanceSource(bmp.getWidth(), bmp.getHeight(),
+                    intArray);
+
+            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+            mCamera.decodeImage(bitmap);
+        } catch (WriterException e) {
+        }
+
+        assertThat(mQrCode).isEqualTo(googleUrl);
+    }
+
+    private Bitmap encodeQrCode(String qrCode, int size) throws WriterException {
+        BitMatrix qrBits = null;
+        try {
+            qrBits =
+                    new MultiFormatWriter().encode(qrCode, BarcodeFormat.QR_CODE, size, size, null);
+        } catch (IllegalArgumentException iae) {
+            // Should never reach here.
+        }
+        assertThat(qrBits).isNotNull();
+
+        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565);
+        for (int x = 0; x < size; ++x) {
+            for (int y = 0; y < size; ++y) {
+                bitmap.setPixel(x, y, qrBits.get(x, y) ? Color.BLACK : Color.WHITE);
+            }
+        }
+        return bitmap;
+    }
+}