Merge "Merge mainline-prod into master"
diff --git a/res/layout/notif_priority_conversation_preference.xml b/res/layout/notif_priority_conversation_preference.xml
index 5da1238..d07bc23 100644
--- a/res/layout/notif_priority_conversation_preference.xml
+++ b/res/layout/notif_priority_conversation_preference.xml
@@ -31,27 +31,32 @@
         android:padding="@dimen/notification_importance_button_padding"
         android:clickable="true"
         android:focusable="true">
-        <ImageView
-            android:id="@+id/icon"
-            android:src="@drawable/ic_important_outline"
-            android:background="@android:color/transparent"
-            android:layout_gravity="center"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:clickable="false"
-            android:focusable="false"/>
-        <TextView
-            android:id="@+id/label"
+        <LinearLayout
+            android:id="@+id/icon_label_container"
+            android:gravity="center_vertical"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:maxLines="1"
-            android:clickable="false"
-            android:focusable="false"
-            android:layout_toEndOf="@id/icon"
-            android:layout_marginStart="@dimen/notification_importance_drawable_padding"
-            android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
-            android:text="@string/notification_priority_title"/>
+            android:orientation="horizontal">
+            <ImageView
+                android:id="@+id/icon"
+                android:src="@drawable/ic_important_outline"
+                android:background="@android:color/transparent"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:clickable="false"
+                android:focusable="false"/>
+            <TextView
+                android:id="@+id/label"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:clickable="false"
+                android:focusable="false"
+                android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+                android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
+                android:text="@string/notification_priority_title"/>
+        </LinearLayout>
         <TextView
             android:id="@+id/summary"
             android:paddingTop="@dimen/notification_importance_button_padding"
@@ -62,7 +67,6 @@
             android:focusable="false"
             android:ellipsize="end"
             android:maxLines="4"
-            android:layout_below="@id/icon"
             android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"
             android:visibility="gone" />
     </com.android.settings.notification.NotificationButtonRelativeLayout>
@@ -75,27 +79,32 @@
         android:layout_marginTop="@dimen/notification_importance_button_separation"
         android:clickable="true"
         android:focusable="true">
-        <ImageView
-            android:id="@+id/icon"
-            android:src="@drawable/ic_notifications_alert"
-            android:background="@android:color/transparent"
-            android:layout_gravity="center"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:clickable="false"
-            android:focusable="false"/>
-        <TextView
-            android:id="@+id/label"
+        <LinearLayout
+            android:id="@+id/icon_label_container"
+            android:gravity="center_vertical"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:maxLines="1"
-            android:clickable="false"
-            android:focusable="false"
-            android:layout_toEndOf="@id/icon"
-            android:layout_marginStart="@dimen/notification_importance_drawable_padding"
-            android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
-            android:text="@string/notification_alert_title"/>
+            android:orientation="horizontal">
+            <ImageView
+                android:id="@+id/icon"
+                android:src="@drawable/ic_notifications_alert"
+                android:background="@android:color/transparent"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:clickable="false"
+                android:focusable="false"/>
+            <TextView
+                android:id="@+id/label"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:clickable="false"
+                android:focusable="false"
+                android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+                android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
+                android:text="@string/notification_alert_title"/>
+        </LinearLayout>
         <TextView
             android:id="@+id/summary"
             android:paddingTop="@dimen/notification_importance_button_padding"
@@ -106,7 +115,7 @@
             android:focusable="false"
             android:ellipsize="end"
             android:maxLines="2"
-            android:layout_below="@id/icon"
+            android:layout_below="@id/icon_label_container"
             android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"
             android:visibility="gone" />
     </com.android.settings.notification.NotificationButtonRelativeLayout>
@@ -119,27 +128,32 @@
         android:layout_marginTop="@dimen/notification_importance_button_separation"
         android:clickable="true"
         android:focusable="true">
-        <ImageView
-            android:id="@+id/icon"
-            android:src="@drawable/ic_notifications_off_24dp"
-            android:background="@android:color/transparent"
-            android:layout_gravity="center"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:clickable="false"
-            android:focusable="false"/>
-        <TextView
-            android:id="@+id/label"
+        <LinearLayout
+            android:id="@+id/icon_label_container"
+            android:gravity="center_vertical"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:maxLines="1"
-            android:clickable="false"
-            android:focusable="false"
-            android:layout_toEndOf="@id/icon"
-            android:layout_marginStart="@dimen/notification_importance_drawable_padding"
-            android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
-            android:text="@string/notification_silence_title"/>
+            android:orientation="horizontal">
+            <ImageView
+                android:id="@+id/icon"
+                android:src="@drawable/ic_notifications_off_24dp"
+                android:background="@android:color/transparent"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:clickable="false"
+                android:focusable="false"/>
+            <TextView
+                android:id="@+id/label"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:clickable="false"
+                android:focusable="false"
+                android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+                android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
+                android:text="@string/notification_silence_title"/>
+        </LinearLayout>
         <TextView
             android:id="@+id/summary"
             android:paddingTop="@dimen/notification_importance_button_padding"
@@ -150,7 +164,7 @@
             android:focusable="false"
             android:ellipsize="end"
             android:maxLines="2"
-            android:layout_below="@id/icon"
+            android:layout_below="@id/icon_label_container"
             android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"
             android:visibility="gone" />
     </com.android.settings.notification.NotificationButtonRelativeLayout>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index e50e9de..41053e4 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -1727,7 +1727,7 @@
     <string name="sms_change_default_no_previous_dialog_text" msgid="6215622785087181275">"Chcete pro zprávy SMS používat aplikaci <xliff:g id="NEW_APP">%s</xliff:g>?"</string>
     <string name="network_scorer_picker_title" msgid="2022922801936206195">"Poskytovatel hodnocení sítí"</string>
     <string name="network_scorer_picker_none_preference" msgid="8894034333043177807">"Žádné"</string>
-    <string name="network_scorer_change_active_dialog_title" msgid="7005220310238618141">"Změnit nastavení Google Wi-Fi Assistant?"</string>
+    <string name="network_scorer_change_active_dialog_title" msgid="7005220310238618141">"Změnit asistenta pro Wi-Fi?"</string>
     <string name="network_scorer_change_active_dialog_text" msgid="7006057749370850706">"Chcete spravovat síťová připojení pomocí aplikace <xliff:g id="NEW_APP">%1$s</xliff:g> namísto aplikace <xliff:g id="CURRENT_APP">%2$s</xliff:g>?"</string>
     <string name="network_scorer_change_active_no_previous_dialog_text" msgid="680685773455072321">"Chcete spravovat síťová připojení pomocí aplikace <xliff:g id="NEW_APP">%s</xliff:g>?"</string>
     <string name="mobile_unknown_sim_operator" msgid="6650422533065760963">"Operátor SIM karty není znám"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 378ac84..d151027 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -3587,7 +3587,7 @@
     <string name="gentle_notifications_display_summary_shade_status_lock" msgid="2068738866725616212">"Zobrazovať v rozbaľovacom paneli, stavovom riadku a na uzamknutej obrazovke"</string>
     <string name="silent_notifications_status_bar" msgid="6113307620588767516">"Skryť tiché upozornenia v stavovom riadku"</string>
     <string name="notification_pulse_title" msgid="8013178454646671529">"Blikať"</string>
-    <string name="lock_screen_notifications_title" msgid="3063951257121435570">"Upozornenia na uzamkn. obraz."</string>
+    <string name="lock_screen_notifications_title" msgid="3063951257121435570">"Upozornenia na uzamknutej obrazovke"</string>
     <string name="lockscreen_bypass_title" msgid="6519964196744088573">"Preskakovať uzamknutú obrazovku"</string>
     <string name="lockscreen_bypass_summary" msgid="6688592486830491144">"Po odomk. tvárou priamo prejsť na poslednú použitú obrazovku"</string>
     <string name="keywords_lockscreen_bypass" msgid="41035425468915498">"Uzamknutá obrazovka, preskočiť, obísť"</string>
diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java
index 02625bb..ab8eea5 100644
--- a/src/com/android/settings/bluetooth/DevicePickerFragment.java
+++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java
@@ -18,6 +18,7 @@
 
 import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
 
+import android.Manifest;
 import android.app.settings.SettingsEnums;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -192,6 +193,6 @@
         if (mLaunchPackage != null && mLaunchClass != null) {
             intent.setClassName(mLaunchPackage, mLaunchClass);
         }
-        getActivity().sendBroadcast(intent);
+        getActivity().sendBroadcast(intent, Manifest.permission.BLUETOOTH_ADMIN);
     }
 }
diff --git a/src/com/android/settings/deviceinfo/OWNERS b/src/com/android/settings/deviceinfo/OWNERS
index bedbe16..e6569f4 100644
--- a/src/com/android/settings/deviceinfo/OWNERS
+++ b/src/com/android/settings/deviceinfo/OWNERS
@@ -1,6 +1,7 @@
 # Default reviewers for this and subdirectories.
 andychou@google.com
 bonianchen@google.com
+goldmanj@google.com
 allenwtsu@google.com
 
 # Emergency approvers in case the above are not available
diff --git a/src/com/android/settings/network/OWNERS b/src/com/android/settings/network/OWNERS
index 87e5fcc..991ad43 100644
--- a/src/com/android/settings/network/OWNERS
+++ b/src/com/android/settings/network/OWNERS
@@ -2,6 +2,7 @@
 allenwtsu@google.com
 andychou@google.com
 bonianchen@google.com
+goldmanj@google.com
 leechou@google.com
 songferngwang@google.com
 tomhsu@google.com
diff --git a/src/com/android/settings/sim/OWNERS b/src/com/android/settings/sim/OWNERS
index bedbe16..e6569f4 100644
--- a/src/com/android/settings/sim/OWNERS
+++ b/src/com/android/settings/sim/OWNERS
@@ -1,6 +1,7 @@
 # Default reviewers for this and subdirectories.
 andychou@google.com
 bonianchen@google.com
+goldmanj@google.com
 allenwtsu@google.com
 
 # Emergency approvers in case the above are not available
diff --git a/src/com/android/settings/wifi/calling/OWNERS b/src/com/android/settings/wifi/calling/OWNERS
index 87e5fcc..991ad43 100644
--- a/src/com/android/settings/wifi/calling/OWNERS
+++ b/src/com/android/settings/wifi/calling/OWNERS
@@ -2,6 +2,7 @@
 allenwtsu@google.com
 andychou@google.com
 bonianchen@google.com
+goldmanj@google.com
 leechou@google.com
 songferngwang@google.com
 tomhsu@google.com
diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
index b862540..9676f58 100644
--- a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
+++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
@@ -165,6 +165,7 @@
     private NetworkInfo mNetworkInfo;
     private NetworkCapabilities mNetworkCapabilities;
     private int mRssiSignalLevel = -1;
+    @VisibleForTesting boolean mShowX; // Shows the Wi-Fi signal icon of Pie+x when it's true.
     private String[] mSignalStr;
     private WifiInfo mWifiInfo;
     private final WifiManager mWifiManager;
@@ -554,7 +555,7 @@
     }
 
     private void refreshRssiViews() {
-        int signalLevel = mWifiEntry.getLevel();
+        final int signalLevel = mWifiEntry.getLevel();
 
         // Disappears signal view if not in range. e.g. for saved networks.
         if (signalLevel == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
@@ -563,11 +564,14 @@
             return;
         }
 
-        if (mRssiSignalLevel == signalLevel) {
+        final boolean showX = mWifiEntry.shouldShowXLevelIcon();
+
+        if (mRssiSignalLevel == signalLevel && mShowX == showX) {
             return;
         }
         mRssiSignalLevel = signalLevel;
-        Drawable wifiIcon = mIconInjector.getIcon(mRssiSignalLevel);
+        mShowX = showX;
+        Drawable wifiIcon = mIconInjector.getIcon(mShowX, mRssiSignalLevel);
 
         if (mEntityHeaderController != null) {
             mEntityHeaderController
@@ -1003,8 +1007,8 @@
             mContext = context;
         }
 
-        public Drawable getIcon(int level) {
-            return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate();
+        public Drawable getIcon(boolean showX, int level) {
+            return mContext.getDrawable(Utils.getWifiIconResource(showX, level)).mutate();
         }
     }
 
diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
index 9aaaa43..1f4254e 100644
--- a/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
+++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
@@ -299,7 +299,7 @@
                 .thenReturn(mMockHeaderController);
         when(mMockHeaderController.setSecondSummary(nullable(String.class)))
                 .thenReturn(mMockHeaderController);
-        when(mMockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable());
+        when(mMockIconInjector.getIcon(anyBoolean(), anyInt())).thenReturn(new ColorDrawable());
 
         setupMockedPreferenceScreen();
     }
@@ -500,7 +500,7 @@
     public void entityHeader_shouldHaveIconSetForConnectedNetwork() {
         setUpForConnectedNetwork();
         setUpSpyController();
-        Drawable expectedIcon = mMockIconInjector.getIcon(LEVEL);
+        Drawable expectedIcon = mMockIconInjector.getIcon(false /* showX */, LEVEL);
 
         displayAndResume();
 
@@ -510,7 +510,7 @@
     @Test
     public void entityHeader_shouldHaveIconSetForDisconnectedNetwork() {
         setUpForDisconnectedNetwork();
-        Drawable expectedIcon = mMockIconInjector.getIcon(LEVEL);
+        Drawable expectedIcon = mMockIconInjector.getIcon(false /* showX */, LEVEL);
 
         displayAndResume();
 
@@ -615,6 +615,7 @@
 
         displayAndResume();
 
+        assertThat(mController.mShowX).isFalse();
         verify(mMockSignalStrengthPref).setIcon(any(Drawable.class));
     }
 
@@ -624,6 +625,7 @@
 
         displayAndResume();
 
+        assertThat(mController.mShowX).isFalse();
         verify(mMockSignalStrengthPref).setIcon(any(Drawable.class));
     }
 
@@ -633,6 +635,7 @@
 
         displayAndResume();
 
+        assertThat(mController.mShowX).isFalse();
         verify(mMockSignalStrengthPref, never()).setIcon(any(Drawable.class));
     }
 
@@ -645,6 +648,7 @@
 
         displayAndResume();
 
+        assertThat(mController.mShowX).isFalse();
         verify(mMockSignalStrengthPref).setSummary(expectedStrength);
     }
 
@@ -656,6 +660,7 @@
 
         displayAndResume();
 
+        assertThat(mController.mShowX).isFalse();
         verify(mMockSignalStrengthPref).setSummary(expectedStrength);
     }
 
@@ -665,10 +670,25 @@
 
         displayAndResume();
 
+        assertThat(mController.mShowX).isFalse();
         verify(mMockSignalStrengthPref, never()).setSummary(any(String.class));
     }
 
     @Test
+    public void signalStrengthPref_shouldShowXLevelIcon_showXTrue() {
+        setUpForConnectedNetwork();
+        setUpSpyController();
+        final String expectedStrength =
+                mContext.getResources().getStringArray(R.array.wifi_signal)[LEVEL];
+        when(mMockWifiEntry.shouldShowXLevelIcon()).thenReturn(true);
+
+        displayAndResume();
+
+        assertThat(mController.mShowX).isTrue();
+        verify(mMockSignalStrengthPref).setSummary(expectedStrength);
+    }
+
+    @Test
     public void linkSpeedPref_shouldNotShowIfNotSet() {
         setUpForConnectedNetwork();
         setUpSpyController();
@@ -1529,7 +1549,7 @@
         ArgumentCaptor<BitmapDrawable> drawableCaptor =
                 ArgumentCaptor.forClass(BitmapDrawable.class);
         Drawable original = mContext.getDrawable(Utils.getWifiIconResource(LEVEL)).mutate();
-        when(mMockIconInjector.getIcon(anyInt())).thenReturn(original);
+        when(mMockIconInjector.getIcon(anyBoolean(), anyInt())).thenReturn(original);
 
         displayAndResume();
 
@@ -1548,7 +1568,7 @@
         ArgumentCaptor<BitmapDrawable> drawableCaptor =
                 ArgumentCaptor.forClass(BitmapDrawable.class);
         Drawable original = mContext.getDrawable(Utils.getWifiIconResource(LEVEL)).mutate();
-        when(mMockIconInjector.getIcon(anyInt())).thenReturn(original);
+        when(mMockIconInjector.getIcon(anyBoolean(), anyInt())).thenReturn(original);
 
         displayAndResume();
 
diff --git a/tests/unit/src/com/android/settings/datausage/AppPrefLoaderTest.java b/tests/unit/src/com/android/settings/datausage/AppPrefLoaderTest.java
new file mode 100644
index 0000000..902906c
--- /dev/null
+++ b/tests/unit/src/com/android/settings/datausage/AppPrefLoaderTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 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.datausage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+import android.util.ArraySet;
+
+import androidx.preference.Preference;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class AppPrefLoaderTest {
+
+    @Mock
+    private PackageManager mPackageManager;
+
+    private AppPrefLoader mLoader;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        final ArraySet<String> pkgs = new ArraySet<>(2);
+        pkgs.add("pkg0");
+        pkgs.add("pkg1");
+        mLoader = new AppPrefLoader(
+                ApplicationProvider.getApplicationContext(), pkgs, mPackageManager);
+    }
+
+    @Test
+    public void loadInBackground_packageNotFound_shouldReturnEmptySet()
+            throws NameNotFoundException {
+        when(mPackageManager.getApplicationInfo(anyString(), anyInt()))
+            .thenThrow(new NameNotFoundException());
+
+        assertThat(mLoader.loadInBackground()).isEmpty();
+    }
+
+    @Test
+    public void loadInBackground_shouldReturnPreference() throws NameNotFoundException {
+        ApplicationInfo info = mock(ApplicationInfo.class);
+        when(mPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(info);
+        final Drawable drawable = mock(Drawable.class);
+        final String label = "Label1";
+        when(info.loadIcon(mPackageManager)).thenReturn(drawable);
+        when(info.loadLabel(mPackageManager)).thenReturn(label);
+
+        Preference preference = mLoader.loadInBackground().valueAt(0);
+        assertThat(preference.getTitle()).isEqualTo(label);
+        assertThat(preference.getIcon()).isEqualTo(drawable);
+        assertThat(preference.isSelectable()).isFalse();
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java b/tests/unit/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java
new file mode 100644
index 0000000..cdb82a6
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2020 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.network;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Looper;
+import android.telephony.SubscriptionManager;
+
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.wifi.WifiConnectionPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+public class MultiNetworkHeaderControllerTest {
+    private static final String KEY_HEADER = "multi_network_header";
+    private static final int EXPANDED_CHILDREN_COUNT = 5;
+
+    @Mock
+    private PreferenceCategory mPreferenceCategory;
+    @Mock
+    private WifiConnectionPreferenceController mWifiController;
+    @Mock
+    private SubscriptionsPreferenceController mSubscriptionsController;
+    @Mock
+    private SubscriptionManager mSubscriptionManager;
+    @Mock
+    private Lifecycle mLifecycle;
+
+    private Context mContext;
+    private PreferenceManager mPreferenceManager;
+    private PreferenceScreen mPreferenceScreen;
+    private MultiNetworkHeaderController mHeaderController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
+
+        mHeaderController = new MultiNetworkHeaderController(mContext, KEY_HEADER) {
+            @Override
+            WifiConnectionPreferenceController createWifiController(Lifecycle lifecycle) {
+                return mWifiController;
+            }
+
+            @Override
+            SubscriptionsPreferenceController createSubscriptionsController(Lifecycle lifecycle) {
+                return mSubscriptionsController;
+            }
+        };
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        mPreferenceManager = new PreferenceManager(mContext);
+        mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
+        mPreferenceScreen.setInitialExpandedChildrenCount(EXPANDED_CHILDREN_COUNT);
+        when(mPreferenceCategory.getKey()).thenReturn(KEY_HEADER);
+        when(mPreferenceCategory.getPreferenceCount()).thenReturn(3);
+        mPreferenceScreen.addPreference(mPreferenceCategory);
+    }
+
+    @Test
+    public void isAvailable_beforeInitIsCalled_notAvailable() {
+        assertThat(mHeaderController.isAvailable()).isFalse();
+    }
+
+    // When calling displayPreference, the header itself should only be visible if the
+    // subscriptions controller says it is available. This is a helper for test cases of this logic.
+    private void displayPreferenceTest(boolean wifiAvailable, boolean subscriptionsAvailable,
+            boolean setVisibleExpectedValue) {
+        when(mWifiController.isAvailable()).thenReturn(wifiAvailable);
+        when(mSubscriptionsController.isAvailable()).thenReturn(subscriptionsAvailable);
+
+        mHeaderController.init(mLifecycle);
+        mHeaderController.displayPreference(mPreferenceScreen);
+        Assert.assertEquals(mPreferenceCategory.isVisible(), setVisibleExpectedValue);
+    }
+
+    @Test
+    public void displayPreference_bothNotAvailable_categoryIsNotVisible() {
+        displayPreferenceTest(false, false, false);
+    }
+
+    @Test
+    public void displayPreference_wifiAvailableButNotSubscriptions_categoryIsNotVisible() {
+        displayPreferenceTest(true, false, false);
+    }
+
+    @Test
+    public void displayPreference_subscriptionsAvailableButNotWifi_categoryIsVisible() {
+        displayPreferenceTest(false, true, true);
+    }
+
+    @Test
+    public void displayPreference_bothAvailable_categoryIsVisible() {
+        displayPreferenceTest(true, true, true);
+    }
+
+    @Test
+    public void onChildUpdated_subscriptionsBecameAvailable_categoryIsVisible() {
+        when(mSubscriptionsController.isAvailable()).thenReturn(false);
+        mHeaderController.init(mLifecycle);
+        mHeaderController.displayPreference(mPreferenceScreen);
+
+        when(mSubscriptionsController.isAvailable()).thenReturn(true);
+        mHeaderController.onChildrenUpdated();
+
+        Assert.assertTrue(mPreferenceCategory.isVisible());
+        assertThat(mPreferenceScreen.getInitialExpandedChildrenCount()).isEqualTo(
+                EXPANDED_CHILDREN_COUNT + mPreferenceCategory.getPreferenceCount());
+    }
+
+    @Test
+    public void onChildUpdated_subscriptionsBecameUnavailable_categoryIsNotVisible() {
+        when(mSubscriptionsController.isAvailable()).thenReturn(true);
+        mHeaderController.init(mLifecycle);
+        mHeaderController.displayPreference(mPreferenceScreen);
+
+        when(mSubscriptionsController.isAvailable()).thenReturn(false);
+        mHeaderController.onChildrenUpdated();
+
+        Assert.assertFalse(mPreferenceCategory.isVisible());
+        assertThat(mPreferenceScreen.getInitialExpandedChildrenCount()).isEqualTo(
+                EXPANDED_CHILDREN_COUNT);
+    }
+
+    @Test
+    public void onChildUpdated_noExpandedChildCountAndAvailable_doesNotSetExpandedCount() {
+        mPreferenceScreen.setInitialExpandedChildrenCount(Integer.MAX_VALUE);
+
+        when(mSubscriptionsController.isAvailable()).thenReturn(false);
+        mHeaderController.init(mLifecycle);
+        mHeaderController.displayPreference(mPreferenceScreen);
+
+        when(mSubscriptionsController.isAvailable()).thenReturn(true);
+        mHeaderController.onChildrenUpdated();
+
+        // Check that setInitialExpandedChildrenCount was never called.
+        Assert.assertEquals(mPreferenceScreen.getInitialExpandedChildrenCount(), Integer.MAX_VALUE);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/telephony/MmsMessagePreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/MmsMessagePreferenceControllerTest.java
new file mode 100644
index 0000000..6036ec7
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/telephony/MmsMessagePreferenceControllerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 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.network.telephony;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
+
+import androidx.preference.SwitchPreference;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class MmsMessagePreferenceControllerTest {
+    private static final int SUB_ID = 2;
+
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private SubscriptionManager mSubscriptionManager;
+
+    private MmsMessagePreferenceController mController;
+    private SwitchPreference mPreference;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
+        when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
+
+        mPreference = new SwitchPreference(mContext);
+        mController = new MmsMessagePreferenceController(mContext, "mms_message");
+        mController.init(SUB_ID);
+        mPreference.setKey(mController.getPreferenceKey());
+    }
+
+    @Test
+    public void getAvailabilityStatus_invalidSubscription_returnUnavailable() {
+        mController.init(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_mobileDataOn_returnUnavailable() {
+        when(mTelephonyManager.isDataEnabled()).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus(SUB_ID)).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_meteredOff_returnUnavailable() {
+        when(mTelephonyManager.isApnMetered(ApnSetting.TYPE_MMS)).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus(SUB_ID)).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_mobileDataOffWithValidSubId_returnAvailable() {
+        mController.init(SUB_ID);
+        when(mTelephonyManager.isDataEnabled()).thenReturn(false);
+        when(mTelephonyManager.isApnMetered(ApnSetting.TYPE_MMS)).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus(SUB_ID)).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void isChecked_returnDataFromTelephonyManager() {
+        when(mTelephonyManager.isDataEnabledForApn(ApnSetting.TYPE_MMS)).thenReturn(false);
+        assertThat(mController.isChecked()).isFalse();
+
+        when(mTelephonyManager.isDataEnabledForApn(ApnSetting.TYPE_MMS)).thenReturn(true);
+        assertThat(mController.isChecked()).isTrue();
+    }
+
+    @Test
+    public void setChecked_setDataIntoSubscriptionManager() {
+        mController.setChecked(true);
+        verify(mTelephonyManager).setAlwaysAllowMmsData(true);
+
+        mController.setChecked(false);
+        verify(mTelephonyManager).setAlwaysAllowMmsData(false);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/telephony/MobileDataPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/MobileDataPreferenceControllerTest.java
new file mode 100644
index 0000000..0feabf2
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/telephony/MobileDataPreferenceControllerTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2020 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.network.telephony;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.res.Resources;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.preference.SwitchPreference;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+public class MobileDataPreferenceControllerTest {
+    private static final int SUB_ID = 2;
+    private static final int SUB_ID_OTHER = 3;
+
+    @Mock
+    private FragmentManager mFragmentManager;
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private TelephonyManager mInvalidTelephonyManager;
+    @Mock
+    private SubscriptionManager mSubscriptionManager;
+    @Mock
+    private SubscriptionInfo mSubscriptionInfo;
+    @Mock
+    private FragmentTransaction mFragmentTransaction;
+
+    private MobileDataPreferenceController mController;
+    private SwitchPreference mPreference;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
+
+        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
+        doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID);
+        doReturn(mInvalidTelephonyManager).when(mTelephonyManager).createForSubscriptionId(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        doReturn(mFragmentTransaction).when(mFragmentManager).beginTransaction();
+
+        mPreference = new SwitchPreference(mContext);
+        mController = new MobileDataPreferenceController(mContext, "mobile_data");
+        mController.init(mFragmentManager, SUB_ID);
+        mPreference.setKey(mController.getPreferenceKey());
+    }
+
+    @Test
+    public void getAvailabilityStatus_invalidSubscription_returnAvailableUnsearchable() {
+        mController.init(mFragmentManager, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE);
+    }
+
+    @Test
+    public void isDialogNeeded_disableSingleSim_returnFalse() {
+        doReturn(true).when(mTelephonyManager).isDataEnabled();
+        doReturn(mSubscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(SUB_ID);
+        doReturn(1).when(mTelephonyManager).getActiveModemCount();
+
+        assertThat(mController.isDialogNeeded()).isFalse();
+    }
+
+    @Test
+    public void isDialogNeeded_enableNonDefaultSimInMultiSimMode_returnTrue() {
+        doReturn(false).when(mTelephonyManager).isDataEnabled();
+        doReturn(mSubscriptionInfo).when(mSubscriptionManager)
+                .getActiveSubscriptionInfo(SUB_ID);
+        // Ideally, it would be better if we could set the default data subscription to
+        // SUB_ID_OTHER, and set that as an active subscription id.
+        when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+
+        assertThat(mController.isDialogNeeded()).isTrue();
+        assertThat(mController.mDialogType).isEqualTo(
+                MobileDataDialogFragment.TYPE_MULTI_SIM_DIALOG);
+    }
+
+    @Test
+    public void handlePreferenceTreeClick_needDialog_showDialog() {
+        mController.mNeedDialog = true;
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        instrumentation.runOnMainSync(() -> {
+            mController.handlePreferenceTreeClick(mPreference);
+        });
+        verify(mFragmentManager).beginTransaction();
+    }
+
+    @Test
+    public void onPreferenceChange_singleSim_On_shouldEnableData() {
+        doReturn(true).when(mTelephonyManager).isDataEnabled();
+        doReturn(mSubscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(SUB_ID);
+        doReturn(1).when(mTelephonyManager).getActiveModemCount();
+
+        mController.onPreferenceChange(mPreference, true);
+
+        verify(mTelephonyManager).setDataEnabled(true);
+    }
+
+    @Test
+    public void onPreferenceChange_multiSim_On_shouldEnableData() {
+        doReturn(true).when(mTelephonyManager).isDataEnabled();
+        doReturn(mSubscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(SUB_ID);
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+
+        mController.onPreferenceChange(mPreference, true);
+
+        verify(mTelephonyManager).setDataEnabled(true);
+    }
+
+    @Test
+    public void isChecked_returnUserDataEnabled() {
+        mController.init(mFragmentManager, SUB_ID);
+        assertThat(mController.isChecked()).isFalse();
+
+        doReturn(true).when(mTelephonyManager).isDataEnabled();
+        assertThat(mController.isChecked()).isTrue();
+    }
+
+    @Test
+    public void updateState_opportunistic_disabled() {
+        doReturn(mSubscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(SUB_ID);
+        mController.init(mFragmentManager, SUB_ID);
+        doReturn(true).when(mSubscriptionInfo).isOpportunistic();
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isEnabled()).isFalse();
+        assertThat(mPreference.getSummary())
+                .isEqualTo(resourceString("mobile_data_settings_summary_auto_switch"));
+    }
+
+    @Test
+    public void updateState_notOpportunistic_enabled() {
+        doReturn(mSubscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(SUB_ID);
+        mController.init(mFragmentManager, SUB_ID);
+        doReturn(false).when(mSubscriptionInfo).isOpportunistic();
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isEnabled()).isTrue();
+        assertThat(mPreference.getSummary())
+                .isEqualTo(resourceString("mobile_data_settings_summary"));
+    }
+
+    public String resourceString(String name) {
+        final Resources res = mContext.getResources();
+        return res.getString(res.getIdentifier(name, "string", mContext.getPackageName()));
+    }
+}