Add toggle for enabling/diabling mobile data

Bug: 117229760
Test: Build, Robolectric
Change-Id: I04b92b1f36e3fe92fb92851b2ed17e3cd7dcefb8
diff --git a/res/values/preference_keys.xml b/res/values/preference_keys.xml
index 8c7ffa6..b5e0651 100644
--- a/res/values/preference_keys.xml
+++ b/res/values/preference_keys.xml
@@ -43,6 +43,7 @@
 
     <!-- Network -->
     <string name="pk_mobile_network_settings_entry" translatable="false">mobile_network_settings_entry</string>
+    <string name="pk_mobile_data_toggle" translatable="false">mobile_data_toggle</string>
     <string name="pk_data_usage_settings_entry" translatable="false">data_usage_settings_entry</string>
 
     <!-- WIFI -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0ed3be3..10a5349 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -39,6 +39,12 @@
     <string name="network_and_internet">Network &amp; internet</string>
     <!-- Mobile network settings [CHAR LIMIT=30] -->
     <string name="mobile_network_settings">Mobile network</string>
+    <!-- Mobile network screen, toggle mobile data title [CHAR LIMIT=30] -->
+    <string name="mobile_network_toggle_title">Mobile data</string>
+    <!-- Mobile network screen, toggle mobile data summary [CHAR LIMIT=50] -->
+    <string name="mobile_network_toggle_summary">Access data using mobile network</string>
+    <!-- Mobile network screen, confirmation dialog to turn off mobile data [CHAR LIMIT=50] -->
+    <string name="confirm_mobile_data_disable">Turn off mobile data?</string>
     <!-- Data usage settings [CHAR LIMIT=30] -->
     <string name="data_usage_settings">Data usage</string>
 
diff --git a/res/xml/mobile_network_fragment.xml b/res/xml/mobile_network_fragment.xml
index 77253a7..675ecb0 100644
--- a/res/xml/mobile_network_fragment.xml
+++ b/res/xml/mobile_network_fragment.xml
@@ -17,4 +17,11 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:title="@string/mobile_network_settings"/>
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/mobile_network_settings">
+    <SwitchPreference
+        android:key="@string/pk_mobile_data_toggle"
+        android:summary="@string/mobile_network_toggle_summary"
+        android:title="@string/mobile_network_toggle_title"
+        settings:controller="com.android.car.settings.network.MobileDataTogglePreferenceController"/>
+</PreferenceScreen>
diff --git a/src/com/android/car/settings/network/ConfirmMobileDataDisableDialog.java b/src/com/android/car/settings/network/ConfirmMobileDataDisableDialog.java
new file mode 100644
index 0000000..f0898f8
--- /dev/null
+++ b/src/com/android/car/settings/network/ConfirmMobileDataDisableDialog.java
@@ -0,0 +1,89 @@
+/*
+ * 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.car.settings.network;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+
+import com.android.car.settings.R;
+
+/** Dialog to confirm disabling mobile data. */
+public class ConfirmMobileDataDisableDialog extends DialogFragment implements
+        DialogInterface.OnClickListener {
+
+    /**
+     * Tag used to open and identify the dialog fragment from the FragmentManager or
+     * FragmentController.
+     */
+    public static final String TAG = "ConfirmMobileDataDisableDialog";
+
+    private ConfirmMobileDataDisableListener mListener;
+
+    /**
+     * Sets a listener which will determine how to handle the user action when they press ok or
+     * cancel.
+     */
+    public void setListener(ConfirmMobileDataDisableListener listener) {
+        mListener = listener;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        return new AlertDialog.Builder(getContext())
+                .setMessage(R.string.confirm_mobile_data_disable)
+                .setPositiveButton(android.R.string.ok, this)
+                .setNegativeButton(android.R.string.cancel, this)
+                .create();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            if (mListener != null) {
+                mListener.onMobileDataDisableConfirmed();
+            }
+        }
+        if (which == DialogInterface.BUTTON_NEGATIVE) {
+            if (mListener != null) {
+                mListener.onMobileDataDisableRejected();
+            }
+        }
+    }
+
+    /**
+     * Interface for listeners that want to receive a callback when user confirms to disable mobile
+     * data.
+     */
+    public interface ConfirmMobileDataDisableListener {
+        /**
+         * Method called only when user presses confirm button.
+         */
+        void onMobileDataDisableConfirmed();
+
+        /**
+         * Method called only when user presses cancel button.
+         */
+        void onMobileDataDisableRejected();
+    }
+}
diff --git a/src/com/android/car/settings/network/MobileDataTogglePreferenceController.java b/src/com/android/car/settings/network/MobileDataTogglePreferenceController.java
new file mode 100644
index 0000000..ebb9f36
--- /dev/null
+++ b/src/com/android/car/settings/network/MobileDataTogglePreferenceController.java
@@ -0,0 +1,91 @@
+/*
+ * 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.car.settings.network;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Business logic to control the toggle that enables/disables usage of mobile data. Does not have
+ * support for multi-sim.
+ */
+public class MobileDataTogglePreferenceController extends
+        PreferenceController<TwoStatePreference> implements
+        ConfirmMobileDataDisableDialog.ConfirmMobileDataDisableListener {
+
+    private TelephonyManager mTelephonyManager;
+
+    public MobileDataTogglePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        ConfirmMobileDataDisableDialog dialog =
+                (ConfirmMobileDataDisableDialog) getFragmentController().findDialogByTag(
+                        ConfirmMobileDataDisableDialog.TAG);
+        if (dialog != null) {
+            dialog.setListener(this);
+        }
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(mTelephonyManager.isDataEnabled());
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean newToggleValue = (Boolean) newValue;
+        if (!newToggleValue) {
+            ConfirmMobileDataDisableDialog dialog = new ConfirmMobileDataDisableDialog();
+            dialog.setListener(this);
+            getFragmentController().showDialog(dialog, ConfirmMobileDataDisableDialog.TAG);
+        } else {
+            setMobileDataEnabled(true);
+        }
+        return false;
+    }
+
+    @Override
+    public void onMobileDataDisableConfirmed() {
+        setMobileDataEnabled(false);
+    }
+
+    @Override
+    public void onMobileDataDisableRejected() {
+        refreshUi();
+    }
+
+    private void setMobileDataEnabled(boolean enabled) {
+        mTelephonyManager.setDataEnabled(enabled);
+        refreshUi();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/network/ConfirmMobileDataDisableDialogTest.java b/tests/robotests/src/com/android/car/settings/network/ConfirmMobileDataDisableDialogTest.java
new file mode 100644
index 0000000..8cd7370
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/network/ConfirmMobileDataDisableDialogTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.car.settings.network;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.BaseTestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.shadows.ShadowDialog;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ConfirmMobileDataDisableDialogTest {
+
+    private ConfirmMobileDataDisableDialog mDialog;
+    @Mock
+    private ConfirmMobileDataDisableDialog.ConfirmMobileDataDisableListener mListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDialog = new ConfirmMobileDataDisableDialog();
+        mDialog.setListener(mListener);
+    }
+
+    @Test
+    public void confirmDisable_disableCallbackCalled() {
+        AlertDialog dialog = showDialog(mDialog);
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        verify(mListener).onMobileDataDisableConfirmed();
+    }
+
+    @Test
+    public void rejectDisable_rejectCallbackCalled() {
+        AlertDialog dialog = showDialog(mDialog);
+        dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
+
+        verify(mListener).onMobileDataDisableRejected();
+    }
+
+    private AlertDialog showDialog(ConfirmMobileDataDisableDialog fragment) {
+        BaseTestActivity activity = Robolectric.setupActivity(BaseTestActivity.class);
+        activity.showDialog(fragment, /* tag= */ null);
+        return (AlertDialog) ShadowDialog.getLatestDialog();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/network/MobileDataTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/network/MobileDataTogglePreferenceControllerTest.java
new file mode 100644
index 0000000..7329d79
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/network/MobileDataTogglePreferenceControllerTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.car.settings.network;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowTelephonyManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowTelephonyManager.class})
+public class MobileDataTogglePreferenceControllerTest {
+
+    private Context mContext;
+    private TwoStatePreference mPreference;
+    private PreferenceControllerTestHelper<MobileDataTogglePreferenceController>
+            mControllerHelper;
+    private MobileDataTogglePreferenceController mController;
+    private TelephonyManager mTelephonyManager;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SwitchPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                MobileDataTogglePreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+
+        mTelephonyManager.setDataEnabled(false);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowTelephonyManager.reset();
+    }
+
+    @Test
+    public void refreshUi_dataEnabled_setChecked() {
+        mTelephonyManager.setDataEnabled(true);
+        mController.refreshUi();
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_dataDisabled_setUnchecked() {
+        mTelephonyManager.setDataEnabled(false);
+        mController.refreshUi();
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void handlePreferenceChanged_setFalse_opensDialog() {
+        mPreference.callChangeListener(false);
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmMobileDataDisableDialog.class), eq(ConfirmMobileDataDisableDialog.TAG));
+    }
+
+    @Test
+    public void handlePreferenceChanged_setTrue_enablesData() {
+        mPreference.callChangeListener(true);
+
+        assertThat(mTelephonyManager.isDataEnabled()).isTrue();
+    }
+
+    @Test
+    public void handlePreferenceChanged_setTrue_checksSwitch() {
+        mPreference.setChecked(false);
+        mPreference.callChangeListener(true);
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onMobileDataDisableConfirmed_unchecksSwitch() {
+        mTelephonyManager.setDataEnabled(true);
+        mPreference.setChecked(true);
+
+        mController.onMobileDataDisableConfirmed();
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onMobileDataDisableConfirmed_disablesMobileData() {
+        mTelephonyManager.setDataEnabled(true);
+
+        mController.onMobileDataDisableConfirmed();
+
+        assertThat(mTelephonyManager.isDataEnabled()).isFalse();
+    }
+
+    @Test
+    public void onMobileDataDisableRejected_checksSwitch() {
+        mTelephonyManager.setDataEnabled(true);
+        mPreference.setChecked(false);
+
+        mController.onMobileDataDisableRejected();
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowTelephonyManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowTelephonyManager.java
index 60bd993..7e9decd 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowTelephonyManager.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowTelephonyManager.java
@@ -37,6 +37,7 @@
     public static final String SUBSCRIBER_ID = "test_id";
     private static Map<Integer, Integer> sSubIdsWithResetCalledCount = new HashMap<>();
     private final Map<PhoneStateListener, Integer> mPhoneStateRegistrations = new HashMap<>();
+    private boolean mIsDataEnabled = false;
 
     public static boolean verifyFactoryResetCalled(int subId, int numTimes) {
         if (!sSubIdsWithResetCalledCount.containsKey(subId)) return false;
@@ -65,6 +66,16 @@
     }
 
     @Implementation
+    protected void setDataEnabled(boolean enable) {
+        mIsDataEnabled = enable;
+    }
+
+    @Implementation
+    protected boolean isDataEnabled() {
+        return mIsDataEnabled;
+    }
+
+    @Implementation
     protected void factoryReset(int subId) {
         sSubIdsWithResetCalledCount.put(subId,
                 sSubIdsWithResetCalledCount.getOrDefault(subId, 0) + 1);