Merge "Make Network & internet v2 feature flag be persistent"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0634ff1..69a67ad 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -139,7 +139,11 @@
                   android:label="@string/network_settings_title"
                   android:theme="@style/Theme.Settings.Home"
                   android:launchMode="singleTask">
-            <!-- TODO(b/114749736): add intent filter here and disable the one in telephony -->
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.NETWORK_OPERATOR_SETTINGS" />
+                <action android:name="android.settings.DATA_ROAMING_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
 
         <!-- Alias for launcher activity only, as this belongs to each profile. -->
@@ -2003,9 +2007,8 @@
             </intent-filter>
         </activity>
 
-        <!-- TODO: Is this needed? -->
         <activity android:name="BandMode"
-                  android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+                  android:label="@string/band_mode_title"
                   android:process="com.android.phone">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/res/layout/band_mode.xml b/res/layout/band_mode.xml
index ddbc7ae..b43dd1d 100644
--- a/res/layout/band_mode.xml
+++ b/res/layout/band_mode.xml
@@ -19,7 +19,7 @@
               android:padding="4dip"
               android:gravity="center_horizontal"
               android:layout_width="match_parent"
-              android:layout_height="match_parent">
+              android:layout_height="wrap_content">
 
     <ListView android:id="@+id/band"
               android:layout_width="match_parent"
diff --git a/res/layout/manage_applications_apps.xml b/res/layout/manage_applications_apps.xml
index 765ced6..c2f58c3 100644
--- a/res/layout/manage_applications_apps.xml
+++ b/res/layout/manage_applications_apps.xml
@@ -14,60 +14,73 @@
      limitations under the License.
 -->
 
-<LinearLayout
+<androidx.coordinatorlayout.widget.CoordinatorLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
 
-    <FrameLayout
+    <androidx.core.widget.NestedScrollView
         android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1">
+        android:layout_height="match_parent"
+        android:fillViewport="true"
+        settings:layout_behavior="com.android.settings.widget.FloatingAppBarScrollingViewBehavior">
 
-        <LinearLayout
-            android:id="@+id/list_container"
+        <FrameLayout
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:orientation="vertical"
-            android:visibility="gone">
+            android:paddingTop="@dimen/app_bar_height">
 
-            <FrameLayout
-                android:id="@+id/pinned_header"
+            <LinearLayout
+                android:id="@+id/list_container"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:visibility="gone">
 
-            <FrameLayout
-                android:layout_width="match_parent"
-                android:layout_height="0px"
-                android:layout_weight="1">
-
-                <androidx.recyclerview.widget.RecyclerView
-                    android:id="@+id/apps_list"
+                <FrameLayout
                     android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    settings:fastScrollEnabled="true"
-                    settings:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
-                    settings:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
-                    settings:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
-                    settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable"/>
+                    android:layout_height="match_parent">
 
-                <TextView
-                    android:id="@android:id/empty"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:gravity="center"
-                    android:text="@string/no_applications"
-                    android:textAppearance="?android:attr/textAppearanceLarge"
-                    android:visibility="invisible" />
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/apps_list"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        settings:fastScrollEnabled="true"
+                        settings:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
+                        settings:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
+                        settings:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
+                        settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable"/>
 
-            </FrameLayout>
+                    <TextView
+                        android:id="@android:id/empty"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:gravity="center"
+                        android:layout_gravity="center"
+                        android:text="@string/no_applications"
+                        android:textAppearance="?android:attr/textAppearanceLarge"
+                        android:visibility="invisible"/>
 
-        </LinearLayout>
+                </FrameLayout>
 
-        <include layout="@layout/loading_container" />
+            </LinearLayout>
 
-    </FrameLayout>
+            <include layout="@layout/loading_container"/>
 
-</LinearLayout>
+        </FrameLayout>
+
+    </androidx.core.widget.NestedScrollView>
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <FrameLayout
+            android:id="@+id/pinned_header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            settings:layout_scrollFlags="scroll|enterAlways"/>
+    </com.google.android.material.appbar.AppBarLayout>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b250eff..1a3d6ff 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -114,6 +114,8 @@
     <dimen name="wifi_assistant_height">182dp</dimen>
     <dimen name="wifi_assistant_image_top">32dp</dimen>
     <dimen name="wifi_assistant_image_start">24dp</dimen>
+    <!-- appbar height is equal search bar height (48dp) plus search bar top and bottom margin  -->
+    <dimen name="app_bar_height">80dp</dimen>
 
     <!-- CryptKeeper top margin for password/pin screen -->
     <dimen name="crypt_keeper_password_top_margin">88dip</dimen>
@@ -354,7 +356,7 @@
     <dimen name="homepage_condition_full_card_padding_bottom">12dp</dimen>
     <dimen name="homepage_condition_header_padding_top">10dp</dimen>
     <dimen name="homepage_condition_header_padding_bottom">10dp</dimen>
-    <dimen name="homepage_condition_header_icons_margin_start">24dp</dimen>
+    <dimen name="homepage_condition_header_icons_margin_start">16dp</dimen>
     <dimen name="homepage_condition_header_indicator_padding_top">4dp</dimen>
     <dimen name="homepage_condition_header_indicator_padding_start">16dp</dimen>
     <dimen name="homepage_condition_header_indicator_padding_end">16dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 026f7ba..c80ba95 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7142,8 +7142,8 @@
     <!-- Sound: Other sounds: Title for the option enabling touch sounds for screen locking sounds. [CHAR LIMIT=30] -->
     <string name="screen_locking_sounds_title">Screen locking sounds</string>
 
-    <!-- Sound: Other sounds: Title for the option enabling charging sounds. [CHAR LIMIT=30] -->
-    <string name="charging_sounds_title">Charging sounds</string>
+    <!-- Sound: Other sounds: Title for the option enabling charging sounds and vibration. [CHAR LIMIT=30] -->
+    <string name="charging_sounds_title">Charging sounds and vibration</string>
 
     <!-- Sound: Other sounds: Title for the option enabling docking sounds. [CHAR LIMIT=30] -->
     <string name="docking_sounds_title">Docking sounds</string>
@@ -9473,10 +9473,14 @@
     <string name="notification_log_details_parcel">parcel size</string>
     <!-- Notification log debug tool: notification ashmem size -->
     <string name="notification_log_details_ashmem">ashmem</string>
+    <!-- Notification log debug tool: header: notification alert info -->
+    <string name="notification_log_details_alerted">notification alerted</string>
     <!-- Notification log debug tool: header: notification sound info -->
     <string name="notification_log_details_sound">sound</string>
     <!-- Notification log debug tool: header: notification vibration info -->
     <string name="notification_log_details_vibrate">vibrate</string>
+    <!-- Notification log debug tool: header: notification vibration info -->
+    <string name="notification_log_details_vibrate_pattern">pattern</string>
     <!-- Notification log debug tool: the word 'default' -->
     <string name="notification_log_details_default">default</string>
     <!-- Notification log debug tool: the word 'none' -->
@@ -10061,6 +10065,11 @@
     <!-- UI debug setting: app selected to use Game Update Package [CHAR LIMIT=NONE] -->
     <string name="gup_dev_opt_in_app_set"><xliff:g id="app_name" example="com.company.app">%1$s</xliff:g></string>
 
+    <!-- Title for Game Update Packages dashboard where developers can configure apps to use GUP or not [CHAR LIMIT=50] -->
+    <string name="gup_dashboard_title">Game Update Packages Preferences</string>
+    <!-- Summary for Game Update Packages dashboard [CHAR LIMIT=50] -->
+    <string name="gup_dashboard_summary">Modify Game Update Packages settings</string>
+
     <!-- Slices Strings -->
 
     <!-- Summary text on a card explaining that a setting does not exist / is not supported on the device [CHAR_LIMIT=NONE]-->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index c15a3fb..111cdbd 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -454,7 +454,8 @@
 
     <style name="ConditionFullCardBorderlessButton"
            parent="@style/ConditionCardBorderlessButton">
-        <item name="android:textAlignment">viewEnd</item>
+        <item name="android:minWidth">24dp</item>
+        <item name="android:layout_marginStart">20dp</item>
     </style>
 
     <style name="ContextualCardDismissalButton"
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index 214c00c..2eedca5 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -191,6 +191,12 @@
             android:title="@string/enable_gpu_debug_layers"
             android:summary="@string/enable_gpu_debug_layers_summary" />
 
+        <Preference
+            android:key="gup_dashboard"
+            android:title="@string/gup_dashboard_title"
+            android:summary="@string/gup_dashboard_summary"
+            android:fragment="com.android.settings.development.gup.GupDashboard" />
+
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/res/xml/gup_settings.xml b/res/xml/gup_settings.xml
new file mode 100644
index 0000000..6344adb
--- /dev/null
+++ b/res/xml/gup_settings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/gup_dashboard_title" />
diff --git a/res/xml/prevent_ringing_gesture_settings.xml b/res/xml/prevent_ringing_gesture_settings.xml
index 62f0223..4b0edc3 100644
--- a/res/xml/prevent_ringing_gesture_settings.xml
+++ b/res/xml/prevent_ringing_gesture_settings.xml
@@ -26,8 +26,11 @@
         app:animation="@raw/gesture_prevent_ringing"
         app:preview="@drawable/gesture_prevent_ringing" />
 
+    <com.android.settingslib.widget.LayoutPreference
+        android:key="gesture_prevent_ringing_switch"
+        android:layout="@layout/styled_switch_bar" />
+
     <PreferenceCategory
         android:key="gesture_prevent_ringing_category"
-        android:title="@string/gesture_prevent_ringing_title">
-    </PreferenceCategory>
+        android:title="@string/gesture_prevent_ringing_title" />
 </PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/BandMode.java b/src/com/android/settings/BandMode.java
index 5be82f6..bae8860 100644
--- a/src/com/android/settings/BandMode.java
+++ b/src/com/android/settings/BandMode.java
@@ -73,13 +73,8 @@
         super.onCreate(icicle);
 
         requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-
         setContentView(R.layout.band_mode);
 
-        setTitle(getString(R.string.band_mode_title));
-        getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT,
-                                    WindowManager.LayoutParams.WRAP_CONTENT);
-
         mPhone = PhoneFactory.getDefaultPhone();
 
         mBandList = (ListView) findViewById(R.id.band);
diff --git a/src/com/android/settings/development/gup/GupDashboard.java b/src/com/android/settings/development/gup/GupDashboard.java
new file mode 100644
index 0000000..674a0a9
--- /dev/null
+++ b/src/com/android/settings/development/gup/GupDashboard.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 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.gup;
+
+import android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import java.util.List;
+
+public class GupDashboard extends DashboardFragment {
+    private static final String TAG = "GupDashboard";
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.SETTINGS_GUP_DASHBOARD;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.gup_settings;
+    }
+
+    @Override
+    public int getHelpResource() {
+        return 0;
+    }
+}
diff --git a/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java b/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java
index cb9bf4f..8f037ce 100644
--- a/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java
+++ b/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java
@@ -46,7 +46,7 @@
         OnResume, OnPause, OnCreate, PreferenceControllerMixin {
 
     @VisibleForTesting static final String KEY_VIBRATE = "prevent_ringing_option_vibrate";
-    @VisibleForTesting static final String KEY_NONE = "prevent_ringing_option_none";
+
     @VisibleForTesting static final String KEY_MUTE = "prevent_ringing_option_mute";
 
     private final String KEY_VIDEO_PAUSED = "key_video_paused";
@@ -57,9 +57,8 @@
     private VideoPreference mVideoPreference;
     private boolean mVideoPaused;
 
-    private PreferenceCategory mPreferenceCategory;
+    @VisibleForTesting PreferenceCategory mPreferenceCategory;
     @VisibleForTesting RadioButtonPreference mVibratePref;
-    @VisibleForTesting RadioButtonPreference mNonePref;
     @VisibleForTesting RadioButtonPreference mMutePref;
 
     private SettingObserver mSettingObserver;
@@ -80,7 +79,6 @@
             mPreferenceCategory = (PreferenceCategory) screen.findPreference(getPreferenceKey());
             mVibratePref = makeRadioPreference(KEY_VIBRATE, R.string.prevent_ringing_option_vibrate);
             mMutePref = makeRadioPreference(KEY_MUTE, R.string.prevent_ringing_option_mute);
-            mNonePref = makeRadioPreference(KEY_NONE, R.string.prevent_ringing_option_none);
 
             if (mPreferenceCategory != null) {
                 mSettingObserver = new SettingObserver(mPreferenceCategory);
@@ -124,19 +122,21 @@
     public void updateState(Preference preference) {
         int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE);
-
         final boolean isVibrate = preventRingingSetting == Settings.Secure.VOLUME_HUSH_VIBRATE;
         final boolean isMute = preventRingingSetting == Settings.Secure.VOLUME_HUSH_MUTE;
-        final boolean isOff = preventRingingSetting == Settings.Secure.VOLUME_HUSH_OFF
-                || (!isVibrate && !isMute);
         if (mVibratePref != null && mVibratePref.isChecked() != isVibrate) {
             mVibratePref.setChecked(isVibrate);
         }
         if (mMutePref != null && mMutePref.isChecked() != isMute) {
             mMutePref.setChecked(isMute);
         }
-        if (mNonePref != null && mNonePref.isChecked() != isOff) {
-            mNonePref.setChecked(isOff);
+
+        if (preventRingingSetting == Settings.Secure.VOLUME_HUSH_OFF) {
+            mVibratePref.setEnabled(false);
+            mMutePref.setEnabled(false);
+        } else {
+            mVibratePref.setEnabled(true);
+            mMutePref.setEnabled(true);
         }
     }
 
@@ -173,13 +173,12 @@
 
     private int keyToSetting(String key) {
         switch (key) {
-            case KEY_NONE:
-                return Settings.Secure.VOLUME_HUSH_OFF;
             case KEY_MUTE:
                 return Settings.Secure.VOLUME_HUSH_MUTE;
             case KEY_VIBRATE:
-            default:
                 return Settings.Secure.VOLUME_HUSH_VIBRATE;
+            default:
+                return Settings.Secure.VOLUME_HUSH_OFF;
         }
     }
 
diff --git a/src/com/android/settings/gestures/PreventRingingGestureSettings.java b/src/com/android/settings/gestures/PreventRingingGestureSettings.java
index 3e8ae85..420a019 100644
--- a/src/com/android/settings/gestures/PreventRingingGestureSettings.java
+++ b/src/com/android/settings/gestures/PreventRingingGestureSettings.java
@@ -35,7 +35,6 @@
 public class PreventRingingGestureSettings extends DashboardFragment {
 
     private static final String TAG = "RingingGestureSettings";
-    private static final String KEY_PREVENT_RINGING = "gesture_prevent_ringing";
 
     @Override
     public void onAttach(Context context) {
@@ -51,6 +50,7 @@
             Lifecycle lifecycle) {
         List<AbstractPreferenceController> controllers = new ArrayList<>();
         controllers.add(new PreventRingingGesturePreferenceController(context, lifecycle));
+        controllers.add(new PreventRingingSwitchPreferenceController(context));
         return controllers;
     }
 
diff --git a/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java b/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java
new file mode 100644
index 0000000..b94cfff
--- /dev/null
+++ b/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 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.gestures;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.widget.Switch;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.widget.SwitchBar;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.widget.LayoutPreference;
+
+public class PreventRingingSwitchPreferenceController extends AbstractPreferenceController
+    implements PreferenceControllerMixin, SwitchBar.OnSwitchChangeListener {
+
+    private static final String KEY = "gesture_prevent_ringing_switch";
+    private final Context mContext;
+    private SettingObserver mSettingObserver;
+
+    @VisibleForTesting SwitchBar mSwitch;
+
+    public PreventRingingSwitchPreferenceController(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        if (isAvailable()) {
+            LayoutPreference pref = (LayoutPreference) screen.findPreference(getPreferenceKey());
+            if (pref != null) {
+                mSettingObserver = new SettingObserver(pref);
+                mSwitch = pref.findViewById(R.id.switch_bar);
+                if (mSwitch != null) {
+                    mSwitch.addOnSwitchChangeListener(this);
+                    mSwitch.show();
+                }
+            }
+        }
+    }
+
+    public void setChecked(boolean isChecked) {
+        if (mSwitch != null) {
+            mSwitch.setChecked(isChecked);
+        }
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE);
+        setChecked(preventRingingSetting != Settings.Secure.VOLUME_HUSH_OFF);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_volumeHushGestureEnabled);
+    }
+
+    @Override
+    public void onSwitchChanged(Switch switchView, boolean isChecked) {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.VOLUME_HUSH_GESTURE, isChecked ? Settings.Secure.VOLUME_HUSH_VIBRATE
+                        : Settings.Secure.VOLUME_HUSH_OFF);
+    }
+
+    private class SettingObserver extends ContentObserver {
+        private final Uri VOLUME_HUSH_GESTURE = Settings.Secure.getUriFor(
+                Settings.Secure.VOLUME_HUSH_GESTURE);
+
+        private final Preference mPreference;
+
+        public SettingObserver(Preference preference) {
+            super(new Handler());
+            mPreference = preference;
+        }
+
+        public void register(ContentResolver cr) {
+            cr.registerContentObserver(VOLUME_HUSH_GESTURE, false, this);
+        }
+
+        public void unregister(ContentResolver cr) {
+            cr.unregisterContentObserver(this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange, uri);
+            if (uri == null || VOLUME_HUSH_GESTURE.equals(uri)) {
+                updateState(mPreference);
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
index 88478e3..49e2a76 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
@@ -107,12 +107,12 @@
                 }
             }
         }
-        return getFinalDisplayableCards(result);
+        return getDisplayableCards(result);
     }
 
     // Get final displayed cards and log what cards will be displayed/hidden
     @VisibleForTesting
-    List<ContextualCard> getFinalDisplayableCards(List<ContextualCard> candidates) {
+    List<ContextualCard> getDisplayableCards(List<ContextualCard> candidates) {
         final List<ContextualCard> eligibleCards = filterEligibleCards(candidates);
         final List<ContextualCard> visibleCards = new ArrayList<>();
         final List<ContextualCard> hiddenCards = new ArrayList<>();
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java
index a4a8419..90974f6 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java
@@ -84,7 +84,11 @@
                         LegacySuggestionContextualCardController.class,
                         LegacySuggestionContextualCardRenderer.class));
                 add(new ControllerRendererMapping(CardType.SLICE,
-                        SliceContextualCardRenderer.VIEW_TYPE,
+                        SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH,
+                        SliceContextualCardController.class,
+                        SliceContextualCardRenderer.class));
+                add(new ControllerRendererMapping(CardType.SLICE,
+                        SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH,
                         SliceContextualCardController.class,
                         SliceContextualCardRenderer.class));
                 add(new ControllerRendererMapping(CardType.CONDITIONAL_FOOTER,
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
index 067de7c..12088f8 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
@@ -17,6 +17,7 @@
 package com.android.settings.homepage.contextualcards;
 
 import static com.android.settings.homepage.contextualcards.ContextualCardLoader.CARD_CONTENT_LOADER_ID;
+import static com.android.settings.intelligence.ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE;
 
 import static java.util.stream.Collectors.groupingBy;
 
@@ -172,7 +173,8 @@
 
         //replace with the new data
         mContextualCards.clear();
-        mContextualCards.addAll(sortCards(allCards));
+        final List<ContextualCard> sortedCards = sortCards(allCards);
+        mContextualCards.addAll(assignCardWidth(sortedCards));
 
         loadCardControllers();
 
@@ -224,6 +226,24 @@
         mListener = listener;
     }
 
+    @VisibleForTesting
+    List<ContextualCard> assignCardWidth(List<ContextualCard> cards) {
+        final List<ContextualCard> result = new ArrayList<>(cards);
+        // Shows as half cards if 2 suggestion type of cards are next to each other.
+        // Shows as full card if 1 suggestion type of card lives alone.
+        for (int index = 1; index < result.size(); index++) {
+            final ContextualCard previous = result.get(index - 1);
+            final ContextualCard current = result.get(index);
+            if (current.getCategory() == SUGGESTION_VALUE
+                    && previous.getCategory() == SUGGESTION_VALUE) {
+                result.set(index - 1, previous.mutate().setIsHalfWidth(true).build());
+                result.set(index, current.mutate().setIsHalfWidth(true).build());
+                index++;
+            }
+        }
+        return result;
+    }
+
     private List<ContextualCard> getCardsToKeep(List<ContextualCard> cards) {
         if (mSavedCards != null) {
             //screen rotate
diff --git a/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java b/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java
index 834ebbc..b477d52 100644
--- a/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java
+++ b/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java
@@ -40,7 +40,7 @@
  */
 public class ConditionContextualCardController implements ContextualCardController,
         ConditionListener, LifecycleObserver, OnStart, OnStop {
-    public static final int EXPANDING_THRESHOLD = 2;
+    public static final int EXPANDING_THRESHOLD = 0;
 
     private static final double UNSUPPORTED_RANKING = -99999.0;
     private static final String TAG = "ConditionCtxCardCtrl";
diff --git a/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java b/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java
index ab1b4c9..531501b 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java
@@ -82,8 +82,8 @@
             return buildBatteryGoodSlice(sliceBuilder, true);
         }
 
-        final List<BatteryTip> batteryTips = SliceBackgroundWorker.getInstance(mContext,
-                this).getResults();
+        final SliceBackgroundWorker worker = SliceBackgroundWorker.getInstance(getUri());
+        final List<BatteryTip> batteryTips = worker != null ? worker.getResults() : null;
 
         if (batteryTips == null) {
             // Because we need wait slice background worker return data
diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java
index 7ca6e9a..5a43f66 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java
@@ -26,7 +26,6 @@
 import android.widget.Button;
 import android.widget.ViewFlipper;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleObserver;
@@ -35,40 +34,40 @@
 import androidx.lifecycle.OnLifecycleEvent;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.slice.Slice;
-import androidx.slice.SliceItem;
-import androidx.slice.widget.EventInfo;
 import androidx.slice.widget.SliceLiveData;
-import androidx.slice.widget.SliceView;
 
 import com.android.settings.R;
 import com.android.settings.homepage.contextualcards.CardContentProvider;
 import com.android.settings.homepage.contextualcards.ContextualCard;
-import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
 import com.android.settings.homepage.contextualcards.ContextualCardRenderer;
 import com.android.settings.homepage.contextualcards.ControllerRendererPool;
-import com.android.settings.overlay.FeatureFactory;
 
 import java.util.Map;
 import java.util.Set;
 
 /**
- * Card renderer for {@link ContextualCard} built as slices.
+ * Card renderer for {@link ContextualCard} built as slice full card or slice half card.
  */
-public class SliceContextualCardRenderer implements ContextualCardRenderer,
-        SliceView.OnSliceActionListener, LifecycleObserver {
-    public static final int VIEW_TYPE = R.layout.homepage_slice_tile;
+public class SliceContextualCardRenderer implements ContextualCardRenderer, LifecycleObserver {
+    public static final int VIEW_TYPE_FULL_WIDTH = R.layout.homepage_slice_tile;
+    public static final int VIEW_TYPE_HALF_WIDTH = R.layout.homepage_slice_half_tile;
 
     private static final String TAG = "SliceCardRenderer";
 
     @VisibleForTesting
     final Map<Uri, LiveData<Slice>> mSliceLiveDataMap;
     @VisibleForTesting
-    final Set<SliceViewHolder> mFlippedCardSet;
+    final Set<RecyclerView.ViewHolder> mFlippedCardSet;
 
     private final Context mContext;
     private final LifecycleOwner mLifecycleOwner;
     private final ControllerRendererPool mControllerRendererPool;
     private final Set<ContextualCard> mCardSet;
+    private final SliceFullCardRendererHelper mFullCardHelper;
+    private final SliceHalfCardRendererHelper mHalfCardHelper;
+
+    //TODO(b/121303357): Remove isHalfWidth field from SliceContextualCardRenderer class.
+    private boolean mIsHalfWidth;
 
     public SliceContextualCardRenderer(Context context, LifecycleOwner lifecycleOwner,
             ControllerRendererPool controllerRendererPool) {
@@ -79,21 +78,26 @@
         mCardSet = new ArraySet<>();
         mFlippedCardSet = new ArraySet<>();
         mLifecycleOwner.getLifecycle().addObserver(this);
+        mFullCardHelper = new SliceFullCardRendererHelper(context);
+        mHalfCardHelper = new SliceHalfCardRendererHelper(context);
     }
 
     @Override
     public int getViewType(boolean isHalfWidth) {
-        return VIEW_TYPE;
+        mIsHalfWidth = isHalfWidth;
+        return isHalfWidth? VIEW_TYPE_HALF_WIDTH : VIEW_TYPE_FULL_WIDTH;
     }
 
     @Override
     public RecyclerView.ViewHolder createViewHolder(View view) {
-        return new SliceViewHolder(view);
+        if (mIsHalfWidth) {
+            return mHalfCardHelper.createViewHolder(view);
+        }
+        return mFullCardHelper.createViewHolder(view);
     }
 
     @Override
     public void bindView(RecyclerView.ViewHolder holder, ContextualCard card) {
-        final SliceViewHolder cardHolder = (SliceViewHolder) holder;
         final Uri uri = card.getSliceUri();
         //TODO(b/120629936): Take this out once blank card issue is fixed.
         Log.d(TAG, "bindView - uri = " + uri);
@@ -103,10 +107,6 @@
             return;
         }
 
-        cardHolder.sliceView.setScrollable(false);
-        cardHolder.sliceView.setTag(uri);
-        //TODO(b/114009676): We will soon have a field to decide what slice mode we should set.
-        cardHolder.sliceView.setMode(SliceView.MODE_LARGE);
         LiveData<Slice> sliceLiveData = mSliceLiveDataMap.get(uri);
 
         if (sliceLiveData == null) {
@@ -125,82 +125,58 @@
                 //TODO(b/120629936): Take this out once blank card issue is fixed.
                 Log.d(TAG, "Slice callback - uri = " + slice.getUri());
             }
-            cardHolder.sliceView.setSlice(slice);
+            if (holder.getItemViewType() == VIEW_TYPE_HALF_WIDTH) {
+                mHalfCardHelper.bindView(holder, card, slice);
+            } else {
+                mFullCardHelper.bindView(holder, card, slice, mCardSet);
+            }
         });
 
-        // Set this listener so we can log the interaction users make on the slice
-        cardHolder.sliceView.setOnSliceActionListener(this);
-
-        // Customize slice view for Settings
-        cardHolder.sliceView.showTitleItems(true);
-        if (card.isLargeCard()) {
-            cardHolder.sliceView.showHeaderDivider(true);
-            cardHolder.sliceView.showActionDividers(true);
+        if (holder.getItemViewType() == VIEW_TYPE_HALF_WIDTH) {
+            initDismissalActions(holder, card, R.id.content);
+        } else {
+            initDismissalActions(holder, card, R.id.slice_view);
         }
-
-        initDismissalActions(cardHolder, card);
     }
 
-    private void initDismissalActions(SliceViewHolder cardHolder, ContextualCard card) {
-        cardHolder.sliceView.setOnLongClickListener(v -> {
-            cardHolder.viewFlipper.showNext();
-            mFlippedCardSet.add(cardHolder);
+    private void initDismissalActions(RecyclerView.ViewHolder holder, ContextualCard card,
+            int initialViewId) {
+        // initialView is the first view in the ViewFlipper.
+        final View initialView = holder.itemView.findViewById(initialViewId);
+        initialView.setOnLongClickListener(v -> {
+            flipCardToDismissalView(holder);
+            mFlippedCardSet.add(holder);
             return true;
         });
 
-        final Button btnKeep = cardHolder.itemView.findViewById(R.id.keep);
+        final Button btnKeep = holder.itemView.findViewById(R.id.keep);
         btnKeep.setOnClickListener(v -> {
-            cardHolder.resetCard();
-            mFlippedCardSet.remove(cardHolder);
+            mFlippedCardSet.remove(holder);
+            resetCardView(holder);
         });
 
-        final Button btnRemove = cardHolder.itemView.findViewById(R.id.remove);
+        final Button btnRemove = holder.itemView.findViewById(R.id.remove);
         btnRemove.setOnClickListener(v -> {
             mControllerRendererPool.getController(mContext, card.getCardType()).onDismissed(card);
-            cardHolder.resetCard();
-            mFlippedCardSet.remove(cardHolder);
+            mFlippedCardSet.remove(holder);
+            resetCardView(holder);
             mSliceLiveDataMap.get(card.getSliceUri()).removeObservers(mLifecycleOwner);
         });
     }
 
-    @Override
-    public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) {
-        //TODO(b/79698338): Log user interaction
-
-        // sliceItem.getSlice().getUri() is like
-        // content://android.settings.slices/action/wifi/_gen/0/_gen/0
-        // contextualCard.getSliceUri() is prefix of sliceItem.getSlice().getUri()
-        for (ContextualCard card : mCardSet) {
-            if (sliceItem.getSlice().getUri().toString().startsWith(
-                    card.getSliceUri().toString())) {
-                ContextualCardFeatureProvider contexualCardFeatureProvider =
-                        FeatureFactory.getFactory(mContext)
-                                .getContextualCardFeatureProvider(mContext);
-                contexualCardFeatureProvider.logContextualCardClick(card,
-                        eventInfo.rowIndex, eventInfo.actionType);
-                break;
-            }
-        }
-    }
-
     @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
     public void onStop() {
-        mFlippedCardSet.stream().forEach(holder -> holder.resetCard());
+        mFlippedCardSet.stream().forEach(holder -> resetCardView(holder));
         mFlippedCardSet.clear();
     }
 
-    public static class SliceViewHolder extends RecyclerView.ViewHolder {
-        public final SliceView sliceView;
-        public final ViewFlipper viewFlipper;
+    private void resetCardView(RecyclerView.ViewHolder holder) {
+        final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper);
+        viewFlipper.setDisplayedChild(0 /* whichChild */);
+    }
 
-        public SliceViewHolder(View view) {
-            super(view);
-            sliceView = view.findViewById(R.id.slice_view);
-            viewFlipper = view.findViewById(R.id.view_flipper);
-        }
-
-        public void resetCard() {
-            viewFlipper.setDisplayedChild(0);
-        }
+    private void flipCardToDismissalView(RecyclerView.ViewHolder holder) {
+        final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper);
+        viewFlipper.showNext();
     }
 }
diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java
new file mode 100644
index 0000000..ef0a67d
--- /dev/null
+++ b/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 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.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.SliceItem;
+import androidx.slice.widget.EventInfo;
+import androidx.slice.widget.SliceView;
+
+import com.android.settings.R;
+import com.android.settings.homepage.contextualcards.ContextualCard;
+import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+
+import java.util.Set;
+
+/**
+ * Card renderer helper for {@link ContextualCard} built as slice full card.
+ */
+class SliceFullCardRendererHelper implements SliceView.OnSliceActionListener {
+    private static final String TAG = "SliceFCRendererHelper";
+
+    private final Context mContext;
+
+    private Set<ContextualCard> mCardSet;
+
+    SliceFullCardRendererHelper(Context context) {
+        mContext = context;
+    }
+
+    RecyclerView.ViewHolder createViewHolder(View view) {
+        return new SliceViewHolder(view);
+    }
+
+    void bindView(RecyclerView.ViewHolder holder, ContextualCard card, Slice slice,
+            Set<ContextualCard> cardSet) {
+        final SliceViewHolder cardHolder = (SliceViewHolder) holder;
+        cardHolder.sliceView.setScrollable(false);
+        cardHolder.sliceView.setTag(card.getSliceUri());
+        //TODO(b/114009676): We will soon have a field to decide what slice mode we should set.
+        cardHolder.sliceView.setMode(SliceView.MODE_LARGE);
+        cardHolder.sliceView.setSlice(slice);
+        mCardSet = cardSet;
+        // Set this listener so we can log the interaction users make on the slice
+        cardHolder.sliceView.setOnSliceActionListener(this);
+
+        // Customize slice view for Settings
+        cardHolder.sliceView.showTitleItems(true);
+        if (card.isLargeCard()) {
+            cardHolder.sliceView.showHeaderDivider(true);
+            cardHolder.sliceView.showActionDividers(true);
+        }
+    }
+
+    @Override
+    public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) {
+        // sliceItem.getSlice().getUri() is like
+        // content://android.settings.slices/action/wifi/_gen/0/_gen/0
+        // contextualCard.getSliceUri() is prefix of sliceItem.getSlice().getUri()
+        final ContextualCardFeatureProvider contextualCardFeatureProvider =
+                FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext);
+        for (ContextualCard card : mCardSet) {
+            if (sliceItem.getSlice().getUri().toString().startsWith(
+                    card.getSliceUri().toString())) {
+                contextualCardFeatureProvider.logContextualCardClick(card, eventInfo.rowIndex,
+                        eventInfo.actionType);
+                break;
+            }
+        }
+    }
+
+    static class SliceViewHolder extends RecyclerView.ViewHolder {
+        public final SliceView sliceView;
+
+        public SliceViewHolder(View view) {
+            super(view);
+            sliceView = view.findViewById(R.id.slice_view);
+        }
+    }
+}
diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java
new file mode 100644
index 0000000..24d654d
--- /dev/null
+++ b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 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.app.PendingIntent;
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.SliceMetadata;
+import androidx.slice.core.SliceAction;
+import androidx.slice.widget.EventInfo;
+
+import com.android.settings.R;
+import com.android.settings.homepage.contextualcards.ContextualCard;
+import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+
+/**
+ * Card renderer helper for {@link ContextualCard} built as slice half card.
+ */
+class SliceHalfCardRendererHelper {
+    private static final String TAG = "SliceHCRendererHelper";
+
+    private final Context mContext;
+
+    SliceHalfCardRendererHelper(Context context) {
+        mContext = context;
+    }
+
+    RecyclerView.ViewHolder createViewHolder(View view) {
+        return new HalfCardViewHolder(view);
+    }
+
+    void bindView(RecyclerView.ViewHolder holder, ContextualCard card, Slice slice) {
+        final HalfCardViewHolder view = (HalfCardViewHolder) holder;
+        final SliceMetadata sliceMetadata = SliceMetadata.from(mContext, slice);
+        final SliceAction primaryAction = sliceMetadata.getPrimaryAction();
+        view.icon.setImageDrawable(primaryAction.getIcon().loadDrawable(mContext));
+        view.title.setText(primaryAction.getTitle());
+        view.content.setOnClickListener(v -> {
+            try {
+                primaryAction.getAction().send();
+            } catch (PendingIntent.CanceledException e) {
+                Log.w(TAG, "Failed to start intent " + primaryAction.getTitle());
+            }
+            final ContextualCardFeatureProvider contextualCardFeatureProvider =
+                    FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext);
+            contextualCardFeatureProvider.logContextualCardClick(card, 0 /* row */,
+                    EventInfo.ACTION_TYPE_CONTENT);
+        });
+    }
+
+    static class HalfCardViewHolder extends RecyclerView.ViewHolder {
+        public final LinearLayout content;
+        public final ImageView icon;
+        public final TextView title;
+
+        public HalfCardViewHolder(View itemView) {
+            super(itemView);
+            content = itemView.findViewById(R.id.content);
+            icon = itemView.findViewById(android.R.id.icon);
+            title = itemView.findViewById(android.R.id.title);
+        }
+    }
+}
diff --git a/src/com/android/settings/location/AppLocationPermissionPreferenceController.java b/src/com/android/settings/location/AppLocationPermissionPreferenceController.java
index 5bfc584..1fd1986 100644
--- a/src/com/android/settings/location/AppLocationPermissionPreferenceController.java
+++ b/src/com/android/settings/location/AppLocationPermissionPreferenceController.java
@@ -6,9 +6,10 @@
 
 import android.content.Context;
 import android.location.LocationManager;
-import android.permission.RuntimePermissionPresenter;
+import android.permission.PermissionControllerManager;
 import android.provider.Settings;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 
 import com.android.settings.R;
@@ -24,9 +25,11 @@
 
     private static final String KEY_APP_LEVEL_PERMISSIONS = "app_level_permissions";
     /** Total number of apps that has location permission. */
-    private int mNumTotal = -1;
+    @VisibleForTesting
+    int mNumTotal = -1;
     /** Total number of apps that has background location permission. */
-    private int mNumBackground = -1;
+    @VisibleForTesting
+    int mNumBackground = -1;
     private final LocationManager mLocationManager;
     private Preference mPreference;
 
@@ -70,7 +73,9 @@
         if (!mLocationManager.isLocationEnabled()) {
             return;
         }
-        RuntimePermissionPresenter.getInstance(mContext).countPermissionApps(
+        PermissionControllerManager permController =
+                mContext.getSystemService(PermissionControllerManager.class);
+        permController.countPermissionApps(
                 Arrays.asList(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION), false, false,
                 (numApps) -> {
                     mNumTotal = numApps;
@@ -79,7 +84,7 @@
                     }
                 }, null);
 
-        RuntimePermissionPresenter.getInstance(mContext).countPermissionApps(
+        permController.countPermissionApps(
                 Collections.singletonList(ACCESS_BACKGROUND_LOCATION), true, false,
                 (numApps) -> {
                     mNumBackground = numApps;
diff --git a/src/com/android/settings/location/TopLevelLocationPreferenceController.java b/src/com/android/settings/location/TopLevelLocationPreferenceController.java
index 455af21..6d7789f 100644
--- a/src/com/android/settings/location/TopLevelLocationPreferenceController.java
+++ b/src/com/android/settings/location/TopLevelLocationPreferenceController.java
@@ -8,7 +8,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.location.LocationManager;
-import android.permission.RuntimePermissionPresenter;
+import android.permission.PermissionControllerManager;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
@@ -70,7 +70,7 @@
         if (!mLocationManager.isLocationEnabled()) {
             return;
         }
-        RuntimePermissionPresenter.getInstance(mContext).countPermissionApps(
+        mContext.getSystemService(PermissionControllerManager.class).countPermissionApps(
                 Arrays.asList(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION), false, false,
                 (numApps) -> {
                     setLocationAppCount(numApps);
diff --git a/src/com/android/settings/network/telephony/MobileDataDialogFragment.java b/src/com/android/settings/network/telephony/MobileDataDialogFragment.java
index be2da04..276d1fb 100644
--- a/src/com/android/settings/network/telephony/MobileDataDialogFragment.java
+++ b/src/com/android/settings/network/telephony/MobileDataDialogFragment.java
@@ -17,6 +17,7 @@
 package com.android.settings.network.telephony;
 
 import android.app.Dialog;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
@@ -109,8 +110,7 @@
 
     @Override
     public int getMetricsCategory() {
-        //TODO(b/114749736): add metric id for this fragment
-        return 0;
+        return SettingsEnums.MOBILE_DATA_DIALOG;
     }
 
     @Override
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index 623b6de..6e5dece 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -196,7 +196,6 @@
         }
     }
 
-    //TODO(b/114749736): update search provider
     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
             new BaseSearchIndexProvider() {
                 @Override
diff --git a/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java b/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java
index f60f927..0326f42 100644
--- a/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java
+++ b/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java
@@ -116,8 +116,6 @@
     }
 
     private int getPreferredNetworkModeSummaryResId(int NetworkMode) {
-        //TODO(b/114749736): refactor it to "Preferred network mode: <Mode>", instead of building
-        // string for each type...
         switch (NetworkMode) {
             case TelephonyManager.NETWORK_MODE_TDSCDMA_GSM_WCDMA:
                 return R.string.preferred_network_mode_tdscdma_gsm_wcdma_summary;
diff --git a/src/com/android/settings/network/telephony/RoamingDialogFragment.java b/src/com/android/settings/network/telephony/RoamingDialogFragment.java
index 4c82686..c349c1a 100644
--- a/src/com/android/settings/network/telephony/RoamingDialogFragment.java
+++ b/src/com/android/settings/network/telephony/RoamingDialogFragment.java
@@ -17,6 +17,7 @@
 
 import android.app.AlertDialog;
 import android.app.Dialog;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
@@ -78,8 +79,7 @@
 
     @Override
     public int getMetricsCategory() {
-        //TODO(b/114749736): add category for roaming dialog
-        return 0;
+        return SettingsEnums.MOBILE_ROAMING_DIALOG;
     }
 
     @Override
diff --git a/src/com/android/settings/nfc/AndroidBeamPreferenceController.java b/src/com/android/settings/nfc/AndroidBeamPreferenceController.java
index 181faa5..b784dc5 100644
--- a/src/com/android/settings/nfc/AndroidBeamPreferenceController.java
+++ b/src/com/android/settings/nfc/AndroidBeamPreferenceController.java
@@ -16,6 +16,7 @@
 package com.android.settings.nfc;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.nfc.NfcAdapter;
 
 import androidx.preference.PreferenceScreen;
@@ -54,6 +55,10 @@
     @Override
     @AvailabilityStatus
     public int getAvailabilityStatus() {
+        PackageManager pm = mContext.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)) {
+                return UNSUPPORTED_ON_DEVICE;
+        }
         return mNfcAdapter != null
                 ? AVAILABLE
                 : UNSUPPORTED_ON_DEVICE;
diff --git a/src/com/android/settings/notification/NotificationStation.java b/src/com/android/settings/notification/NotificationStation.java
index d3ebc08..116980f 100644
--- a/src/com/android/settings/notification/NotificationStation.java
+++ b/src/com/android/settings/notification/NotificationStation.java
@@ -16,10 +16,13 @@
 
 package com.android.settings.notification;
 
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -354,28 +357,53 @@
                         getString(R.string.notification_log_details_group_summary)));
             }
         }
-        sb.append("\n")
-                .append(bold(getString(R.string.notification_log_details_sound)))
-                .append(delim);
-        if (0 != (n.defaults & Notification.DEFAULT_SOUND)) {
-            sb.append(getString(R.string.notification_log_details_default));
-        } else if (n.sound != null) {
-            sb.append(n.sound.toString());
-        } else {
-            sb.append(getString(R.string.notification_log_details_none));
-        }
-        sb.append("\n")
-                .append(bold(getString(R.string.notification_log_details_vibrate)))
-                .append(delim);
-        if (0 != (n.defaults & Notification.DEFAULT_VIBRATE)) {
-            sb.append(getString(R.string.notification_log_details_default));
-        } else if (n.vibrate != null) {
-            for (int vi=0;vi<n.vibrate.length;vi++) {
-                if (vi > 0) sb.append(',');
-                sb.append(String.valueOf(n.vibrate[vi]));
+        if (info.active) {
+            // mRanking only applies to active notifications
+            if (mRanking != null && mRanking.getRanking(sbn.getKey(), rank)) {
+                if (rank.getLastAudiblyAlertedMillis() > 0) {
+                    sb.append("\n")
+                            .append(bold(getString(R.string.notification_log_details_alerted)));
+                }
             }
-        } else {
-            sb.append(getString(R.string.notification_log_details_none));
+        }
+        try {
+            NotificationChannel channel = mNoMan.getNotificationChannelForPackage(
+                    sbn.getPackageName(), sbn.getUid(), n.getChannelId(), false);
+            sb.append("\n")
+                    .append(bold(getString(R.string.notification_log_details_sound)))
+                    .append(delim);
+            if (channel.getImportance() == IMPORTANCE_UNSPECIFIED) {
+
+                if (0 != (n.defaults & Notification.DEFAULT_SOUND)) {
+                    sb.append(getString(R.string.notification_log_details_default));
+                } else if (n.sound != null) {
+                    sb.append(n.sound.toString());
+                } else {
+                    sb.append(getString(R.string.notification_log_details_none));
+                }
+            } else {
+                sb.append(String.valueOf(channel.getSound()));
+            }
+            sb.append("\n")
+                    .append(bold(getString(R.string.notification_log_details_vibrate)))
+                    .append(delim);
+            if (channel.getImportance() == IMPORTANCE_UNSPECIFIED) {
+                if (0 != (n.defaults & Notification.DEFAULT_VIBRATE)) {
+                    sb.append(getString(R.string.notification_log_details_default));
+                } else if (n.vibrate != null) {
+                    sb.append(getString(R.string.notification_log_details_vibrate_pattern));
+                } else {
+                    sb.append(getString(R.string.notification_log_details_none));
+                }
+            } else {
+                if (channel.getVibrationPattern() != null) {
+                    sb.append(getString(R.string.notification_log_details_vibrate_pattern));
+                } else {
+                    sb.append(getString(R.string.notification_log_details_none));
+                }
+            }
+        } catch (RemoteException e) {
+            Log.d(TAG, "cannot read channel info", e);
         }
         sb.append("\n")
                 .append(bold(getString(R.string.notification_log_details_visibility)))
diff --git a/src/com/android/settings/slices/SliceBackgroundWorker.java b/src/com/android/settings/slices/SliceBackgroundWorker.java
index 8b68f05..284fd23 100644
--- a/src/com/android/settings/slices/SliceBackgroundWorker.java
+++ b/src/com/android/settings/slices/SliceBackgroundWorker.java
@@ -17,6 +17,7 @@
 package com.android.settings.slices;
 
 import android.annotation.MainThread;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.net.Uri;
 import android.util.ArrayMap;
@@ -58,20 +59,29 @@
         mUri = uri;
     }
 
-    public Uri getUri() {
+    protected Uri getUri() {
         return mUri;
     }
 
     /**
+     * Returns the singleton instance of the {@link SliceBackgroundWorker} for specified {@link Uri}
+     * if exists
+     */
+    @Nullable
+    public static SliceBackgroundWorker getInstance(Uri uri) {
+        return LIVE_WORKERS.get(uri);
+    }
+
+    /**
      * Returns the singleton instance of the {@link SliceBackgroundWorker} for specified {@link
      * CustomSliceable}
      */
-    public static SliceBackgroundWorker getInstance(Context context, CustomSliceable sliceable) {
+    static SliceBackgroundWorker getInstance(Context context, CustomSliceable sliceable) {
         final Uri uri = sliceable.getUri();
-        final Class<? extends SliceBackgroundWorker> workerClass =
-                sliceable.getBackgroundWorkerClass();
-        SliceBackgroundWorker worker = LIVE_WORKERS.get(uri);
+        SliceBackgroundWorker worker = getInstance(uri);
         if (worker == null) {
+            final Class<? extends SliceBackgroundWorker> workerClass =
+                    sliceable.getBackgroundWorkerClass();
             worker = createInstance(context, uri, workerClass);
             LIVE_WORKERS.put(uri, worker);
         }
diff --git a/src/com/android/settings/wifi/slice/WifiSlice.java b/src/com/android/settings/wifi/slice/WifiSlice.java
index 4d3a95a..35bb89f 100644
--- a/src/com/android/settings/wifi/slice/WifiSlice.java
+++ b/src/com/android/settings/wifi/slice/WifiSlice.java
@@ -105,8 +105,8 @@
             return listBuilder.build();
         }
 
-        final List<AccessPoint> results =
-                SliceBackgroundWorker.getInstance(mContext, this).getResults();
+        final SliceBackgroundWorker worker = SliceBackgroundWorker.getInstance(getUri());
+        final List<AccessPoint> results = worker != null ? worker.getResults() : null;
 
         // Need a loading text when results are not ready.
         boolean needLoadingRow = results == null;
diff --git a/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
index fb2ce97..c0a3fa7 100644
--- a/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
+++ b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
@@ -53,10 +53,8 @@
         final WifiConfiguration config = mWifiManager.getWifiApConfiguration();
         if (config != null) {
             mSSID = config.SSID;
-            Log.d(TAG, "Updating SSID in Preference, " + mSSID);
         } else {
             mSSID = DEFAULT_SSID;
-            Log.d(TAG, "Updating to default SSID in Preference, " + mSSID);
         }
         ((ValidatedEditTextPreference) mPreference).setValidator(this);
         updateSsidDisplay((EditTextPreference) mPreference);
diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider
index 447de00..2622eb3 100644
--- a/tests/robotests/assets/grandfather_not_implementing_index_provider
+++ b/tests/robotests/assets/grandfather_not_implementing_index_provider
@@ -28,6 +28,7 @@
 com.android.settings.datausage.AppDataUsage
 com.android.settings.datausage.DataUsageList
 com.android.settings.datetime.timezone.TimeZoneSettings
+com.android.settings.development.gup.GupDashboard
 com.android.settings.deviceinfo.PrivateVolumeSettings
 com.android.settings.deviceinfo.PublicVolumeSettings
 com.android.settings.deviceinfo.StorageProfileFragment
diff --git a/tests/robotests/src/com/android/settings/development/gup/GupDashboardTest.java b/tests/robotests/src/com/android/settings/development/gup/GupDashboardTest.java
new file mode 100644
index 0000000..17278ef
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/gup/GupDashboardTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 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.gup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class GupDashboardTest {
+    private GupDashboard mDashboard;
+
+    @Before
+    public void setUp() {
+        mDashboard = new GupDashboard();
+    }
+
+    @Test
+    public void getHelpResource_shouldReturn0() {
+        assertThat(mDashboard.getHelpResource()).isEqualTo(0);
+    }
+
+    @Test
+    public void getMetricesCategory_shouldReturnGupDashboard() {
+        assertThat(mDashboard.getMetricsCategory())
+                .isEqualTo(MetricsProto.MetricsEvent.SETTINGS_GUP_DASHBOARD);
+    }
+
+    @Test
+    public void getPreferenceScreen_shouldReturnGupSettings() {
+        assertThat(mDashboard.getPreferenceScreenResId())
+                .isEqualTo(R.xml.gup_settings);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java
index 84fef5d..956d8bf 100644
--- a/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java
@@ -18,44 +18,47 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.preference.PreferenceCategory;
 import android.provider.Settings;
 
-import androidx.preference.PreferenceScreen;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
 
 import com.android.settings.widget.RadioButtonPreference;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
 @RunWith(RobolectricTestRunner.class)
 public class PreventRingingGesturePreferenceControllerTest {
-
     private Context mContext;
     private Resources mResources;
     private PreventRingingGesturePreferenceController mController;
 
+    @Mock
+    private Preference mPreference;
+
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         mContext = spy(RuntimeEnvironment.application);
         mResources = mock(Resources.class);
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getBoolean(com.android.internal.R.bool.config_volumeHushGestureEnabled))
                 .thenReturn(true);
         mController = new PreventRingingGesturePreferenceController(mContext, null);
+        mController.mPreferenceCategory = new PreferenceCategory(mContext);
         mController.mVibratePref = new RadioButtonPreference(mContext);
-        mController.mNonePref = new RadioButtonPreference(mContext);
         mController.mMutePref = new RadioButtonPreference(mContext);
     }
 
@@ -79,9 +82,10 @@
     public void testUpdateState_mute() {
         Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
                 Settings.Secure.VOLUME_HUSH_MUTE);
-        mController.updateState(null);
+        mController.updateState(mPreference);
+        assertThat(mController.mVibratePref.isEnabled()).isTrue();
+        assertThat(mController.mMutePref.isEnabled()).isTrue();
         assertThat(mController.mVibratePref.isChecked()).isFalse();
-        assertThat(mController.mNonePref.isChecked()).isFalse();
         assertThat(mController.mMutePref.isChecked()).isTrue();
     }
 
@@ -89,9 +93,21 @@
     public void testUpdateState_vibrate() {
         Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
                 Settings.Secure.VOLUME_HUSH_VIBRATE);
-        mController.updateState(null);
+        mController.updateState(mPreference);
+        assertThat(mController.mVibratePref.isEnabled()).isTrue();
+        assertThat(mController.mMutePref.isEnabled()).isTrue();
         assertThat(mController.mVibratePref.isChecked()).isTrue();
-        assertThat(mController.mNonePref.isChecked()).isFalse();
+        assertThat(mController.mMutePref.isChecked()).isFalse();
+    }
+
+    @Test
+    public void testUpdateState_off() {
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
+                Settings.Secure.VOLUME_HUSH_OFF);
+        mController.updateState(mPreference);
+        assertThat(mController.mVibratePref.isEnabled()).isFalse();
+        assertThat(mController.mMutePref.isEnabled()).isFalse();
+        assertThat(mController.mVibratePref.isChecked()).isFalse();
         assertThat(mController.mMutePref.isChecked()).isFalse();
     }
 
@@ -99,9 +115,8 @@
     public void testUpdateState_other() {
         Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
                 7);
-        mController.updateState(null);
+        mController.updateState(mPreference);
         assertThat(mController.mVibratePref.isChecked()).isFalse();
-        assertThat(mController.mNonePref.isChecked()).isTrue();
         assertThat(mController.mMutePref.isChecked()).isFalse();
     }
 
@@ -132,19 +147,4 @@
                 Settings.Secure.getInt(mContext.getContentResolver(),
                         Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_OFF));
     }
-
-    @Test
-    public void testRadioButtonClicked_off() {
-        RadioButtonPreference rbPref = new RadioButtonPreference(mContext);
-        rbPref.setKey(PreventRingingGesturePreferenceController.KEY_NONE);
-
-        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
-                Settings.Secure.VOLUME_HUSH_MUTE);
-
-        mController.onRadioButtonClicked(rbPref);
-
-        assertThat(Settings.Secure.VOLUME_HUSH_OFF).isEqualTo(
-                Settings.Secure.getInt(mContext.getContentResolver(),
-                        Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE));
-    }
 }
diff --git a/tests/robotests/src/com/android/settings/gestures/PreventRingingSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/gestures/PreventRingingSwitchPreferenceControllerTest.java
new file mode 100644
index 0000000..5f221f5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/gestures/PreventRingingSwitchPreferenceControllerTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 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.gestures;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+
+import com.android.settings.widget.SwitchBar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class PreventRingingSwitchPreferenceControllerTest {
+    private Context mContext;
+    private Resources mResources;
+    private PreventRingingSwitchPreferenceController mController;
+    private Preference mPreference = mock(Preference.class);
+
+    @Before
+    public void setUp() {
+        mContext = spy(RuntimeEnvironment.application);
+        mResources = mock(Resources.class);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getBoolean(com.android.internal.R.bool.config_volumeHushGestureEnabled))
+                .thenReturn(true);
+        mController = new PreventRingingSwitchPreferenceController(mContext);
+        mController.mSwitch = mock(SwitchBar.class);
+    }
+
+    @Test
+    public void testIsAvailable_configIsTrue_shouldReturnTrue() {
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_volumeHushGestureEnabled)).thenReturn(true);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void testIsAvailable_configIsFalse_shouldReturnFalse() {
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_volumeHushGestureEnabled)).thenReturn(false);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testOn_updateState_hushOff() {
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
+                Settings.Secure.VOLUME_HUSH_OFF);
+        mController.updateState(mPreference);
+        verify(mController.mSwitch, times(1)).setChecked(false);
+    }
+
+    @Test
+    public void testOn_updateState_hushVibrate() {
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
+                Settings.Secure.VOLUME_HUSH_VIBRATE);
+        mController.updateState(mPreference);
+        verify(mController.mSwitch, times(1)).setChecked(true);
+    }
+
+    @Test
+    public void testOn_updateState_hushMute() {
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
+                Settings.Secure.VOLUME_HUSH_MUTE);
+        mController.updateState(mPreference);
+        verify(mController.mSwitch, times(1)).setChecked(true);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLoaderTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLoaderTest.java
index c62a6bb..c47fa38 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLoaderTest.java
@@ -29,16 +29,16 @@
 
 import com.android.settings.slices.CustomSliceRegistry;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
 @RunWith(RobolectricTestRunner.class)
 public class ContextualCardLoaderTest {
 
@@ -82,29 +82,29 @@
     }
 
     @Test
-    public void getFinalDisplayableCards_twoEligibleCards_shouldShowAll() {
+    public void getDisplayableCards_twoEligibleCards_shouldShowAll() {
         final List<ContextualCard> cards = getContextualCardList().stream().limit(2)
                 .collect(Collectors.toList());
         doReturn(cards).when(mContextualCardLoader).filterEligibleCards(any(List.class));
 
-        final List<ContextualCard> result = mContextualCardLoader.getFinalDisplayableCards(cards);
+        final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(cards);
 
         assertThat(result).hasSize(cards.size());
     }
 
     @Test
-    public void getFinalDisplayableCards_fiveEligibleCardsNoLarge_shouldShowDefaultCardCount() {
+    public void getDisplayableCards_fiveEligibleCardsNoLarge_shouldShowDefaultCardCount() {
         final List<ContextualCard> fiveCards = getContextualCardListWithNoLargeCard();
         doReturn(fiveCards).when(mContextualCardLoader).filterEligibleCards(any(List.class));
 
-        final List<ContextualCard> result = mContextualCardLoader.getFinalDisplayableCards(
+        final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(
                 fiveCards);
 
         assertThat(result).hasSize(DEFAULT_CARD_COUNT);
     }
 
     @Test
-    public void getFinalDisplayableCards_threeEligibleCardsOneLarge_shouldShowThreeCards() {
+    public void getDisplayableCards_threeEligibleCardsOneLarge_shouldShowThreeCards() {
         final List<ContextualCard> cards = getContextualCardList().stream().limit(2)
                 .collect(Collectors.toList());
         cards.add(new ContextualCard.Builder()
@@ -115,18 +115,18 @@
                 .build());
         doReturn(cards).when(mContextualCardLoader).filterEligibleCards(any(List.class));
 
-        final List<ContextualCard> result = mContextualCardLoader.getFinalDisplayableCards(cards);
+        final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(cards);
 
         assertThat(result).hasSize(3);
     }
 
     @Test
-    public void getFinalDisplayableCards_threeEligibleCardsTwoLarge_shouldShowTwoCards() {
+    public void getDisplayableCards_threeEligibleCardsTwoLarge_shouldShowTwoCards() {
         final List<ContextualCard> threeCards = getContextualCardList().stream().limit(3)
                 .collect(Collectors.toList());
         doReturn(threeCards).when(mContextualCardLoader).filterEligibleCards(any(List.class));
 
-        final List<ContextualCard> result = mContextualCardLoader.getFinalDisplayableCards(
+        final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(
                 threeCards);
 
         assertThat(result).hasSize(2);
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java
index c405ffc..3fc8e06 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java
@@ -32,6 +32,8 @@
 import com.android.settings.homepage.contextualcards.conditional.ConditionFooterContextualCard;
 import com.android.settings.homepage.contextualcards.conditional.ConditionHeaderContextualCard;
 import com.android.settings.homepage.contextualcards.conditional.ConditionalContextualCard;
+import com.android.settings.intelligence.ContextualCardProto;
+import com.android.settings.slices.CustomSliceRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -203,6 +205,134 @@
         assertThat(actualCards).containsExactlyElementsIn(expectedCards);
     }
 
+
+    @Test
+    public void assignCardWidth_noSuggestionCards_shouldNotHaveHalfCards() {
+        final List<Integer> categories = Arrays.asList(
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE
+        );
+        final List<ContextualCard> noSuggestionCards = buildCategoriedCards(getContextualCardList(),
+                categories);
+
+        final List<ContextualCard> result = mManager.assignCardWidth(noSuggestionCards);
+
+        assertThat(result).hasSize(5);
+        for (ContextualCard card : result) {
+            assertThat(card.isHalfWidth()).isFalse();
+        }
+    }
+
+    @Test
+    public void assignCardWidth_oneSuggestionCards_shouldNotHaveHalfCards() {
+        final List<Integer> categories = Arrays.asList(
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE
+        );
+        final List<ContextualCard> oneSuggestionCards = buildCategoriedCards(
+                getContextualCardList(), categories);
+
+        final List<ContextualCard> result = mManager.assignCardWidth(oneSuggestionCards);
+
+        assertThat(result).hasSize(5);
+        for (ContextualCard card : result) {
+            assertThat(card.isHalfWidth()).isFalse();
+        }
+    }
+
+    @Test
+    public void assignCardWidth_twoConsecutiveSuggestionCards_shouldHaveTwoHalfCards() {
+        final List<Integer> categories = Arrays.asList(
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE
+        );
+        final List<ContextualCard> twoConsecutiveSuggestionCards = buildCategoriedCards(
+                getContextualCardList(), categories);
+        final List<Boolean> expectedValues = Arrays.asList(false, false, true, true, false);
+
+        final List<ContextualCard> result = mManager.assignCardWidth(
+                twoConsecutiveSuggestionCards);
+
+        assertThat(result).hasSize(5);
+        for (int i = 0; i < result.size(); i++) {
+            assertThat(result.get(i).isHalfWidth()).isEqualTo(expectedValues.get(i));
+        }
+    }
+
+    @Test
+    public void assignCardWidth_twoNonConsecutiveSuggestionCards_shouldNotHaveHalfCards() {
+        final List<Integer> categories = Arrays.asList(
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE
+        );
+        final List<ContextualCard> twoNonConsecutiveSuggestionCards = buildCategoriedCards(
+                getContextualCardList(), categories);
+
+        final List<ContextualCard> result = mManager.assignCardWidth(
+                twoNonConsecutiveSuggestionCards);
+
+        assertThat(result).hasSize(5);
+        for (ContextualCard card : result) {
+            assertThat(card.isHalfWidth()).isFalse();
+        }
+    }
+
+    @Test
+    public void assignCardWidth_threeConsecutiveSuggestionCards_shouldHaveTwoHalfCards() {
+        final List<Integer> categories = Arrays.asList(
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE
+        );
+        final List<ContextualCard> threeConsecutiveSuggestionCards = buildCategoriedCards(
+                getContextualCardList(), categories);
+        final List<Boolean> expectedValues = Arrays.asList(false, true, true, false, false);
+
+        final List<ContextualCard> result = mManager.assignCardWidth(
+                threeConsecutiveSuggestionCards);
+
+        assertThat(result).hasSize(5);
+        for (int i = 0; i < result.size(); i++) {
+            assertThat(result.get(i).isHalfWidth()).isEqualTo(expectedValues.get(i));
+        }
+    }
+
+    @Test
+    public void assignCardWidth_fourConsecutiveSuggestionCards_shouldHaveFourHalfCards() {
+        final List<Integer> categories = Arrays.asList(
+                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
+                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE
+        );
+        final List<ContextualCard> fourConsecutiveSuggestionCards = buildCategoriedCards(
+                getContextualCardList(), categories);
+        final List<Boolean> expectedValues = Arrays.asList(false, true, true, true, true);
+
+        final List<ContextualCard> result = mManager.assignCardWidth(
+                fourConsecutiveSuggestionCards);
+
+        assertThat(result).hasSize(5);
+        for (int i = 0; i < result.size(); i++) {
+            assertThat(result.get(i).isHalfWidth()).isEqualTo(expectedValues.get(i));
+        }
+    }
+
     private ContextualCard buildContextualCard(String sliceUri) {
         return new ContextualCard.Builder()
                 .setName(TEST_SLICE_NAME)
@@ -210,4 +340,45 @@
                 .setSliceUri(Uri.parse(sliceUri))
                 .build();
     }
+
+    private List<ContextualCard> buildCategoriedCards(List<ContextualCard> cards,
+            List<Integer> categories) {
+        final List<ContextualCard> result = new ArrayList<>();
+        for (int i = 0; i < cards.size(); i++) {
+            result.add(cards.get(i).mutate().setCategory(categories.get(i)).build());
+        }
+        return result;
+    }
+
+    private List<ContextualCard> getContextualCardList() {
+        final List<ContextualCard> cards = new ArrayList<>();
+        cards.add(new ContextualCard.Builder()
+                .setName("test_wifi")
+                .setCardType(ContextualCard.CardType.SLICE)
+                .setSliceUri(CustomSliceRegistry.CONTEXTUAL_WIFI_SLICE_URI)
+                .build());
+        cards.add(new ContextualCard.Builder()
+                .setName("test_flashlight")
+                .setCardType(ContextualCard.CardType.SLICE)
+                .setSliceUri(
+                        Uri.parse("content://com.android.settings.test.slices/action/flashlight"))
+                .build());
+        cards.add(new ContextualCard.Builder()
+                .setName("test_connected")
+                .setCardType(ContextualCard.CardType.SLICE)
+                .setSliceUri(CustomSliceRegistry.BLUETOOTH_DEVICES_SLICE_URI)
+                .build());
+        cards.add(new ContextualCard.Builder()
+                .setName("test_gesture")
+                .setCardType(ContextualCard.CardType.SLICE)
+                .setSliceUri(Uri.parse(
+                        "content://com.android.settings.test.slices/action/gesture_pick_up"))
+                .build());
+        cards.add(new ContextualCard.Builder()
+                .setName("test_battery")
+                .setCardType(ContextualCard.CardType.SLICE)
+                .setSliceUri(CustomSliceRegistry.BATTERY_INFO_SLICE_URI)
+                .build());
+        return cards;
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardControllerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardControllerTest.java
index 566ca07..4553f7c 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardControllerTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardControllerTest.java
@@ -112,7 +112,8 @@
     }
 
     @Test
-    public void getConditionalCards_hasOneConditionCard_shouldGetOneFullWidthCard() {
+    public void getConditionalCards_hasOneConditionCardAndExpanded_shouldGetOneFullWidthCard() {
+        mController.setIsExpanded(true);
         final Map<Integer, List<ContextualCard>> conditionalCards =
                 mController.buildConditionalCardsWithFooterOrHeader(generateConditionCards(1));
 
@@ -120,11 +121,24 @@
         assertThat(conditionalCards.get(CardType.CONDITIONAL)).hasSize(1);
         assertThat(conditionalCards.get(CardType.CONDITIONAL).get(0).isHalfWidth()).isFalse();
         assertThat(conditionalCards.get(CardType.CONDITIONAL_HEADER)).isEmpty();
+        assertThat(conditionalCards.get(CardType.CONDITIONAL_FOOTER)).isNotEmpty();
+    }
+
+    @Test
+    public void getConditionalCards_hasOneConditionCardAndCollapsed_shouldGetConditionalHeader() {
+        mController.setIsExpanded(false);
+        final Map<Integer, List<ContextualCard>> conditionalCards =
+                mController.buildConditionalCardsWithFooterOrHeader(generateConditionCards(1));
+
+        assertThat(conditionalCards).hasSize(3);
+        assertThat(conditionalCards.get(CardType.CONDITIONAL)).isEmpty();
+        assertThat(conditionalCards.get(CardType.CONDITIONAL_HEADER)).isNotEmpty();
         assertThat(conditionalCards.get(CardType.CONDITIONAL_FOOTER)).isEmpty();
     }
 
     @Test
-    public void getConditionalCards_hasTwoConditionCards_shouldGetTwoHalfWidthCards() {
+    public void getConditionalCards_hasTwoConditionCardsAndExpanded_shouldGetTwoHalfWidthCards() {
+        mController.setIsExpanded(true);
         final Map<Integer, List<ContextualCard>> conditionalCards =
                 mController.buildConditionalCardsWithFooterOrHeader(generateConditionCards(2));
 
@@ -134,6 +148,18 @@
             assertThat(card.isHalfWidth()).isTrue();
         }
         assertThat(conditionalCards.get(CardType.CONDITIONAL_HEADER)).isEmpty();
+        assertThat(conditionalCards.get(CardType.CONDITIONAL_FOOTER)).isNotEmpty();
+    }
+
+    @Test
+    public void getConditionalCards_hasTwoConditionCardsAndCollapsed_shouldGetConditionalHeader() {
+        mController.setIsExpanded(false);
+        final Map<Integer, List<ContextualCard>> conditionalCards =
+                mController.buildConditionalCardsWithFooterOrHeader(generateConditionCards(2));
+
+        assertThat(conditionalCards).hasSize(3);
+        assertThat(conditionalCards.get(CardType.CONDITIONAL)).isEmpty();
+        assertThat(conditionalCards.get(CardType.CONDITIONAL_HEADER)).isNotEmpty();
         assertThat(conditionalCards.get(CardType.CONDITIONAL_FOOTER)).isEmpty();
     }
 
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 0b87525..4d9a21d 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
@@ -79,17 +79,6 @@
     }
 
     @Test
-    public void bindView_shouldSetScrollableToFalse() {
-        RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
-
-        mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI));
-
-        assertThat(
-                ((SliceContextualCardRenderer.SliceViewHolder) viewHolder).sliceView.isScrollable
-                        ()).isFalse();
-    }
-
-    @Test
     public void bindView_invalidScheme_sliceShouldBeNull() {
         final Uri sliceUri = Uri.parse("contet://com.android.settings.slices/action/flashlight");
         RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
@@ -97,7 +86,7 @@
         mRenderer.bindView(viewHolder, buildContextualCard(sliceUri));
 
         assertThat(
-                ((SliceContextualCardRenderer.SliceViewHolder) viewHolder).sliceView.getSlice())
+                ((SliceFullCardRendererHelper.SliceViewHolder) viewHolder).sliceView.getSlice())
                 .isNull();
     }
 
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java
new file mode 100644
index 0000000..9172300d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2019 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 com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.SliceProvider;
+import androidx.slice.builders.ListBuilder;
+import androidx.slice.builders.SliceAction;
+import androidx.slice.widget.SliceLiveData;
+import androidx.slice.widget.SliceView;
+
+import com.android.settings.R;
+import com.android.settings.homepage.contextualcards.ContextualCard;
+import com.android.settings.homepage.contextualcards.slices.SliceFullCardRendererHelper.SliceViewHolder;
+import com.android.settings.intelligence.ContextualCardProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Collections;
+
+@RunWith(RobolectricTestRunner.class)
+public class SliceFullCardRendererHelperTest {
+
+    private static final Uri TEST_SLICE_URI = Uri.parse("content://test/test");
+
+    private Activity mActivity;
+    private SliceFullCardRendererHelper mHelper;
+
+    @Before
+    public void setUp() {
+        // Set-up specs for SliceMetadata.
+        SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
+        mActivity = Robolectric.buildActivity(Activity.class).create().get();
+        mActivity.setTheme(R.style.Theme_Settings_Home);
+        mHelper = new SliceFullCardRendererHelper(mActivity);
+    }
+
+    @Test
+    public void createViewHolder_shouldAlwaysReturnSliceViewHolder() {
+        final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
+
+        assertThat(viewHolder).isInstanceOf(SliceViewHolder.class);
+    }
+
+    @Test
+    public void bindView_shouldSetScrollableToFalse() {
+        final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
+
+        mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet());
+
+        assertThat(((SliceViewHolder) viewHolder).sliceView.isScrollable()).isFalse();
+    }
+
+    @Test
+    public void bindView_shouldSetTagToSliceUri() {
+        final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
+        final ContextualCard card = buildContextualCard();
+
+        mHelper.bindView(viewHolder, card, buildSlice(), Collections.emptySet());
+
+        assertThat(((SliceViewHolder) viewHolder).sliceView.getTag()).isEqualTo(card.getSliceUri());
+    }
+
+    @Test
+    public void bindView_shouldSetModeToLarge() {
+        final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
+
+        mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet());
+
+        assertThat(((SliceViewHolder) viewHolder).sliceView.getMode()).isEqualTo(
+                SliceView.MODE_LARGE);
+    }
+
+    @Test
+    public void bindView_shouldSetSlice() {
+        final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
+
+        mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet());
+
+        assertThat(((SliceViewHolder) viewHolder).sliceView.getSlice().getUri()).isEqualTo(
+                TEST_SLICE_URI);
+    }
+
+    private RecyclerView.ViewHolder getSliceViewHolder() {
+        final RecyclerView recyclerView = new RecyclerView(mActivity);
+        recyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
+        final View view = LayoutInflater.from(mActivity).inflate(VIEW_TYPE_FULL_WIDTH, recyclerView,
+                false);
+        return mHelper.createViewHolder(view);
+    }
+
+    private ContextualCard buildContextualCard() {
+        return new ContextualCard.Builder()
+                .setName("test_name")
+                .setCategory(ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE)
+                .setCardType(ContextualCard.CardType.SLICE)
+                .setSliceUri(TEST_SLICE_URI)
+                .setIsHalfWidth(false /* isHalfWidth */)
+                .build();
+    }
+
+    private Slice buildSlice() {
+        final String title = "test_title";
+        final IconCompat icon = IconCompat.createWithResource(mActivity, R.drawable.empty_icon);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(
+                mActivity,
+                title.hashCode() /* requestCode */,
+                new Intent("test action"),
+                0  /* flags */);
+        final SliceAction action
+                = SliceAction.createDeeplink(pendingIntent, icon, ListBuilder.SMALL_IMAGE, title);
+        return new ListBuilder(mActivity, TEST_SLICE_URI, ListBuilder.INFINITY)
+                .addRow(new ListBuilder.RowBuilder()
+                        .addEndItem(icon, ListBuilder.ICON_IMAGE)
+                        .setTitle(title)
+                        .setPrimaryAction(action))
+                .build();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelperTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelperTest.java
new file mode 100644
index 0000000..c38697e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelperTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 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 com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.SliceProvider;
+import androidx.slice.builders.ListBuilder;
+import androidx.slice.builders.SliceAction;
+import androidx.slice.widget.SliceLiveData;
+
+import com.android.settings.R;
+import com.android.settings.homepage.contextualcards.ContextualCard;
+import com.android.settings.homepage.contextualcards.slices.SliceHalfCardRendererHelper.HalfCardViewHolder;
+import com.android.settings.intelligence.ContextualCardProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class SliceHalfCardRendererHelperTest {
+
+    private static final Uri TEST_SLICE_URI = Uri.parse("content://test/test");
+
+    private Activity mActivity;
+    private SliceHalfCardRendererHelper mHelper;
+
+    @Before
+    public void setUp() {
+        // Set-up specs for SliceMetadata.
+        SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
+        mActivity = Robolectric.buildActivity(Activity.class).create().get();
+        mActivity.setTheme(R.style.Theme_Settings_Home);
+        mHelper = new SliceHalfCardRendererHelper(mActivity);
+    }
+
+    @Test
+    public void createViewHolder_shouldAlwaysReturnCustomViewHolder() {
+        final RecyclerView.ViewHolder viewHolder = getHalfCardViewHolder();
+
+        assertThat(viewHolder).isInstanceOf(HalfCardViewHolder.class);
+    }
+
+    @Test
+    public void bindView_shouldSetTitle() {
+        final RecyclerView.ViewHolder viewHolder = getHalfCardViewHolder();
+
+        mHelper.bindView(viewHolder, buildContextualCard(), buildSlice());
+
+        assertThat(((HalfCardViewHolder) viewHolder).title.getText()).isEqualTo("test_title");
+    }
+
+    private RecyclerView.ViewHolder getHalfCardViewHolder() {
+        final RecyclerView recyclerView = new RecyclerView(mActivity);
+        recyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
+        final View view = LayoutInflater.from(mActivity).inflate(VIEW_TYPE_HALF_WIDTH, recyclerView,
+                false);
+
+        return mHelper.createViewHolder(view);
+    }
+
+    private ContextualCard buildContextualCard() {
+        return new ContextualCard.Builder()
+                .setName("test_name")
+                .setCategory(ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE)
+                .setCardType(ContextualCard.CardType.SLICE)
+                .setSliceUri(TEST_SLICE_URI)
+                .setIsHalfWidth(false /* isHalfWidth */)
+                .build();
+    }
+
+    private Slice buildSlice() {
+        final String title = "test_title";
+        final IconCompat icon = IconCompat.createWithResource(mActivity, R.drawable.empty_icon);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(
+                mActivity,
+                title.hashCode() /* requestCode */,
+                new Intent("test action"),
+                0  /* flags */);
+        final SliceAction action
+                = SliceAction.createDeeplink(pendingIntent, icon, ListBuilder.SMALL_IMAGE, title);
+        return new ListBuilder(mActivity, TEST_SLICE_URI, ListBuilder.INFINITY)
+                .addRow(new ListBuilder.RowBuilder()
+                        .addEndItem(icon, ListBuilder.ICON_IMAGE)
+                        .setTitle(title)
+                        .setPrimaryAction(action))
+                .build();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java
index 6379e44..bddd5fe 100644
--- a/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java
@@ -3,10 +3,12 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
+import android.location.LocationManager;
 import android.provider.Settings;
 
 import androidx.lifecycle.LifecycleOwner;
 
+import com.android.settings.R;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -27,6 +29,7 @@
 
     private LifecycleOwner mLifecycleOwner;
     private Lifecycle mLifecycle;
+    private LocationManager mLocationManager;
 
     @Before
     public void setUp() {
@@ -35,6 +38,7 @@
         mLifecycleOwner = () -> mLifecycle;
         mLifecycle = new Lifecycle(mLifecycleOwner);
         mController = new AppLocationPermissionPreferenceController(mContext, mLifecycle);
+        mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
     }
 
     @Test
@@ -52,4 +56,40 @@
 
         assertThat(mController.isAvailable()).isTrue();
     }
+
+    @Test
+    public void getSummary_whenLocationIsOff_shouldReturnStringForOff() {
+        mLocationManager.setLocationEnabledForUser(false, android.os.Process.myUserHandle());
+
+        assertThat(mController.getSummary()).isEqualTo(
+                mContext.getString(R.string.location_app_permission_summary_location_off));
+    }
+
+    @Test
+    public void getSummary_whenLocationIsOn_shouldReturnLoadingString() {
+        mLocationManager.setLocationEnabledForUser(true, android.os.Process.myUserHandle());
+
+        assertThat(mController.getSummary()).isEqualTo(
+                mContext.getString(R.string.location_settings_loading_app_permission_stats));
+    }
+
+    @Test
+    public void getSummary_whenLocationAppCountIsOne_shouldShowSingularString() {
+        mLocationManager.setLocationEnabledForUser(true, android.os.Process.myUserHandle());
+        mController.mNumBackground = 1;
+        mController.mNumTotal = 1;
+
+        assertThat(mController.getSummary()).isEqualTo(mContext.getResources().getQuantityString(
+                R.plurals.location_app_permission_summary_location_on, 1, 1, 1));
+    }
+
+    @Test
+    public void getSummary_whenLocationAppCountIsGreaterThanOne_shouldShowPluralString() {
+        mLocationManager.setLocationEnabledForUser(true, android.os.Process.myUserHandle());
+        mController.mNumBackground = 5;
+        mController.mNumTotal = 10;
+
+        assertThat(mController.getSummary()).isEqualTo(mContext.getResources().getQuantityString(
+                R.plurals.location_app_permission_summary_location_on, 5, 5, 10));
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java
new file mode 100644
index 0000000..3b33558
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 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.location;
+
+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.when;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settingslib.location.RecentLocationAccesses;
+import com.android.settingslib.widget.LayoutPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class RecentLocationAccessPreferenceControllerTest {
+    @Mock
+    private LayoutPreference mLayoutPreference;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private RecentLocationAccesses mRecentLocationApps;
+
+    private Context mContext;
+    private RecentLocationAccessPreferenceController mController;
+    private View mAppEntitiesHeaderView;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        mController = spy(
+                new RecentLocationAccessPreferenceController(mContext, mRecentLocationApps));
+        final String key = mController.getPreferenceKey();
+        mAppEntitiesHeaderView = LayoutInflater.from(mContext).inflate(
+                R.layout.app_entities_header, null /* root */);
+        when(mScreen.findPreference(key)).thenReturn(mLayoutPreference);
+        when(mLayoutPreference.getKey()).thenReturn(key);
+        when(mLayoutPreference.getContext()).thenReturn(mContext);
+        when(mLayoutPreference.findViewById(R.id.app_entities_header)).thenReturn(
+                mAppEntitiesHeaderView);
+    }
+
+    /** Verifies the title text, details text are correct, and the click listener is set. */
+    @Test
+    public void updateState_whenAppListIsEmpty_shouldDisplayTitleTextAndDetailsText() {
+        doReturn(new ArrayList<>()).when(mRecentLocationApps).getAppListSorted();
+        mController.displayPreference(mScreen);
+        mController.updateState(mLayoutPreference);
+
+        final TextView title = mAppEntitiesHeaderView.findViewById(R.id.header_title);
+        assertThat(title.getText()).isEqualTo(
+                mContext.getText(R.string.location_category_recent_location_access));
+        final TextView details = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+        assertThat(details.getText()).isEqualTo(
+                mContext.getText(R.string.location_recent_location_access_view_details));
+        assertThat(details.hasOnClickListeners()).isTrue();
+    }
+
+    @Test
+    public void updateState_whenAppListMoreThanThree_shouldDisplayTopThreeApps() {
+        final List<RecentLocationAccesses.Access> accesses = createMockAccesses(6);
+        doReturn(accesses).when(mRecentLocationApps).getAppListSorted();
+        mController.displayPreference(mScreen);
+        mController.updateState(mLayoutPreference);
+
+        // The widget can display the top 3 apps from the list when there're more than 3.
+        final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
+        final ImageView appIconView1 = app1View.findViewById(R.id.app_icon);
+        final TextView appTitle1 = app1View.findViewById(R.id.app_title);
+        final TextView appSummary1 = app1View.findViewById(R.id.app_summary);
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView1.getDrawable()).isNotNull();
+        assertThat(appTitle1.getText()).isEqualTo("appTitle0");
+        assertThat(appSummary1.getText()).isEqualTo("appSummary0");
+
+        final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
+        final ImageView appIconView2 = app2View.findViewById(R.id.app_icon);
+        final TextView appTitle2 = app2View.findViewById(R.id.app_title);
+        final TextView appSummary2 = app2View.findViewById(R.id.app_summary);
+
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView2.getDrawable()).isNotNull();
+        assertThat(appTitle2.getText()).isEqualTo("appTitle1");
+        assertThat(appSummary2.getText()).isEqualTo("appSummary1");
+
+        final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
+        final ImageView appIconView3 = app3View.findViewById(R.id.app_icon);
+        final TextView appTitle3 = app3View.findViewById(R.id.app_title);
+        final TextView appSummary3 = app3View.findViewById(R.id.app_summary);
+
+        assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView3.getDrawable()).isNotNull();
+        assertThat(appTitle3.getText()).isEqualTo("appTitle2");
+        assertThat(appSummary3.getText()).isEqualTo("appSummary2");
+    }
+
+    private List<RecentLocationAccesses.Access> createMockAccesses(int count) {
+        final List<RecentLocationAccesses.Access> accesses = new ArrayList<>();
+        for (int i = 0; i < count; i++) {
+            final Drawable icon = mock(Drawable.class);
+            // Add mock accesses
+            final RecentLocationAccesses.Access access = new RecentLocationAccesses.Access(
+                    "packageName", android.os.Process.myUserHandle(), icon,
+                    "appTitle" + i, "appSummary" + i, 1000 - i);
+            accesses.add(access);
+        }
+        return accesses;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/nfc/AndroidBeamPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/nfc/AndroidBeamPreferenceControllerTest.java
index 8ed9dcc..cd70d66 100644
--- a/tests/robotests/src/com/android/settings/nfc/AndroidBeamPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/nfc/AndroidBeamPreferenceControllerTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.nfc.NfcAdapter;
 import android.nfc.NfcManager;
 import android.os.UserHandle;
@@ -57,6 +58,8 @@
     private UserManager mUserManager;
     @Mock
     private PreferenceScreen mScreen;
+    @Mock
+    private PackageManager mPackageManager;
 
     private RestrictedPreference mAndroidBeamPreference;
     private AndroidBeamPreferenceController mAndroidBeamController;
@@ -78,6 +81,8 @@
         mAndroidBeamPreference = new RestrictedPreference(RuntimeEnvironment.application);
         when(mScreen.findPreference(mAndroidBeamController.getPreferenceKey())).thenReturn(
                 mAndroidBeamPreference);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)).thenReturn(true);
 
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
@@ -95,6 +100,13 @@
     }
 
     @Test
+    public void isAvailable_noNfcFeature_shouldReturnFalse() {
+        when(mNfcAdapter.isEnabled()).thenReturn(true);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)).thenReturn(false);
+        assertThat(mAndroidBeamController.isAvailable()).isFalse();
+    }
+
+    @Test
     public void isAvailable_noNfcAdapter_shouldReturnFalse() {
         ReflectionHelpers.setField(mAndroidBeamController, "mNfcAdapter", null);
         assertThat(mAndroidBeamController.isAvailable()).isFalse();