Add new dialogue when user is going to delete sim that use RAC.

Test: make
Bug: 316419093
Change-Id: Iaed54afa7cfd20c1dd6adbd4d50f54cab3da095d
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6e24863..00d9f08 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -818,6 +818,10 @@
                   android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
                   android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/>
 
+        <activity android:name=".network.telephony.EuiccRacConnectivityDialogActivity"
+                  android:exported="false"
+                  android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/>
+
         <activity
             android:name="Settings$TetherSettingsActivity"
             android:label="@string/tether_settings_title_all"
diff --git a/res/layout/sim_warning_dialog_wifi_connectivity.xml b/res/layout/sim_warning_dialog_wifi_connectivity.xml
new file mode 100644
index 0000000..c6cdbc7
--- /dev/null
+++ b/res/layout/sim_warning_dialog_wifi_connectivity.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+        <ImageView
+            android:src="@drawable/ic_warning_24dp"
+            android:contentDescription="@null"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="16dp"
+            android:gravity="center"
+            android:tint="?android:attr/textColorSecondary"/>
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingEnd="24dp"
+            android:paddingTop="16dp"
+            android:paddingLeft="24dp"
+            android:gravity="center"
+            style="?android:attr/textAppearanceLarge"/>
+
+        <TextView
+            android:id="@+id/msg"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingEnd="24dp"
+            android:paddingTop="16dp"
+            android:paddingStart="24dp"
+            android:paddingBottom="32dp"
+            android:gravity="center"
+            android:textAppearance="@style/TextAppearance.DialogMessage"
+            android:visibility="gone"/>
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 012ddc3..db6e30c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11660,6 +11660,16 @@
     <!-- Body text of error message indicating the device could not erase the SIM due to an error. [CHAR_LIMIT=NONE] -->
     <string name="erase_sim_fail_text">Something went wrong and this eSIM wasn\'t erased.\n\nRestart your device and try again.</string>
 
+    <!-- Strings for to use Wi-Fi before deleting eUICC subscriptions -->
+    <!-- Title on confirmation dialog asking the user to have Wi-Fi. [CHAR_LIMIT=NONE] -->
+    <string name="wifi_warning_dialog_title">Connect to Wi\u2011Fi before erasing</string>
+    <!-- Body text in confirmation dialog indicating why having Wi-Fi is recommended. [CHAR_LIMIT=NONE] -->
+    <string name="wifi_warning_dialog_text">This makes it easier to use your eSIM again in the future without needing to contact your carrier</string>
+    <!-- Button label to continue with erasing [CHAR_LIMIT=20] -->
+    <string name="wifi_warning_continue_button">Erase anyway</string>
+    <!-- Button label to return to settings [CHAR_LIMIT=20] -->
+    <string name="wifi_warning_return_button">OK</string>
+
     <!-- Title for Network connection request Dialog [CHAR LIMIT=60] -->
     <string name="network_connection_request_dialog_title">Connect to device</string>
     <!-- Summary for Network connection request Dialog [CHAR LIMIT=NONE] -->
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index 84e4e75..2498ec9 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -17,13 +17,15 @@
 package com.android.settings.network;
 
 import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
 import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
+import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
 
 import static com.android.internal.util.CollectionUtils.emptyIfNull;
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
 import android.os.ParcelUuid;
 import android.provider.Settings;
 import android.telephony.PhoneNumberUtils;
@@ -560,6 +562,7 @@
             Log.i(TAG, "Unable to delete subscription due to invalid subscription ID.");
             return;
         }
+        // TODO(b/325693582): Add verification if carrier is RAC and logic for new dialog
         context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
     }
 
@@ -832,4 +835,29 @@
         }
         return true;
     }
+
+    /**
+     * Returns {@code true} if device is connected to Wi-Fi or mobile data provided by a different
+     * subId.
+     *
+     * @param context context
+     * @param targetSubId subscription that is going to be deleted
+     */
+    @VisibleForTesting
+    static boolean isConnectedToWifiOrDifferentSubId(@NonNull Context context, int targetSubId) {
+        ConnectivityManager connectivityManager =
+                context.getSystemService(ConnectivityManager.class);
+        NetworkCapabilities capabilities =
+                connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
+
+        if (capabilities != null) {
+            if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+                // Connected to WiFi
+                return true;
+            } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+                return targetSubId != SubscriptionManager.getActiveDataSubscriptionId();
+            }
+        }
+        return false;
+    }
 }
diff --git a/src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java b/src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java
new file mode 100644
index 0000000..cb4ab18
--- /dev/null
+++ b/src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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 android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.R;
+
+/** This dialog activity advise the user to have connectivity if the eSIM uses a RAC. */
+public class EuiccRacConnectivityDialogActivity extends SubscriptionActionDialogActivity
+        implements WarningDialogFragment.OnConfirmListener {
+
+    private static final String TAG = "EuiccRacConnectivityDialogActivity";
+    // Dialog tags
+    private static final int DIALOG_TAG_ERASE_ANYWAY_CONFIRMATION = 1;
+
+    private int mSubId;
+
+    /**
+     * Returns an intent of EuiccRacConnectivityDialogActivity.
+     *
+     * @param context The context used to start the EuiccRacConnectivityDialogActivity.
+     * @param subId The subscription ID of the subscription needs to be deleted. If the subscription
+     *     belongs to a group of subscriptions, all subscriptions from the group will be deleted.
+     */
+    @NonNull
+    public static Intent getIntent(@NonNull Context context, int subId) {
+        Intent intent = new Intent(context, EuiccRacConnectivityDialogActivity.class);
+        intent.putExtra(ARG_SUB_ID, subId);
+        return intent;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        mSubId = intent.getIntExtra(ARG_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        if (savedInstanceState == null) {
+            showConnectivityWarningDialog();
+        }
+    }
+
+    @Override
+    public void onConfirm(int tag, boolean confirmed) {
+        if (!confirmed) {
+            finish();
+            return;
+        }
+
+        switch (tag) {
+            case DIALOG_TAG_ERASE_ANYWAY_CONFIRMATION:
+                finish();
+                Log.i(TAG, "Show dialogue activity that handles deleting eSIM profiles");
+                startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(this, mSubId));
+                break;
+            default:
+                Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag);
+                break;
+        }
+    }
+
+    /* Displays warning to have connectivity because subscription is RAC dialog. */
+    private void showConnectivityWarningDialog() {
+        WarningDialogFragment.show(
+                this,
+                WarningDialogFragment.OnConfirmListener.class,
+                DIALOG_TAG_ERASE_ANYWAY_CONFIRMATION,
+                getString(R.string.wifi_warning_dialog_title),
+                getString(R.string.wifi_warning_dialog_text),
+                getString(R.string.wifi_warning_continue_button),
+                getString(R.string.wifi_warning_return_button));
+    }
+}
diff --git a/src/com/android/settings/network/telephony/WarningDialogFragment.java b/src/com/android/settings/network/telephony/WarningDialogFragment.java
new file mode 100644
index 0000000..58bc1da
--- /dev/null
+++ b/src/com/android/settings/network/telephony/WarningDialogFragment.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 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 android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.settings.R;
+
+/** Fragment to show a warning dialog. The caller should implement onConfirmListener. */
+public class WarningDialogFragment extends BaseDialogFragment
+        implements DialogInterface.OnClickListener {
+    private static final String TAG = "WarningDialogFragment";
+    private static final String ARG_TITLE = "title";
+    private static final String ARG_MSG = "msg";
+    private static final String ARG_POS_BUTTON_STRING = "pos_button_string";
+    private static final String ARG_NEG_BUTTON_STRING = "neg_button_string";
+
+    /**
+     * Interface defining the method that will be invoked when the user has done with the dialog.
+     */
+    public interface OnConfirmListener {
+        /**
+         * @param tag The tag in the caller.
+         * @param confirmed True if the user has clicked the positive button. False if the user has
+         *     clicked the negative button or cancel the dialog.
+         */
+        void onConfirm(int tag, boolean confirmed);
+    }
+
+    /** Displays a confirmation dialog which has confirm and cancel buttons. */
+    static <T> void show(
+            FragmentActivity activity,
+            Class<T> callbackInterfaceClass,
+            int tagInCaller,
+            String title,
+            String msg,
+            String posButtonString,
+            String negButtonString) {
+        WarningDialogFragment fragment = new WarningDialogFragment();
+        Bundle arguments = new Bundle();
+        arguments.putString(ARG_TITLE, title);
+        arguments.putCharSequence(ARG_MSG, msg);
+        arguments.putString(ARG_POS_BUTTON_STRING, posButtonString);
+        arguments.putString(ARG_NEG_BUTTON_STRING, negButtonString);
+        setListener(activity, null, callbackInterfaceClass, tagInCaller, arguments);
+        fragment.setArguments(arguments);
+        fragment.show(activity.getSupportFragmentManager(), TAG);
+    }
+
+    @Override
+    @NonNull
+    public final Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        String title = getArguments().getString(ARG_TITLE);
+        String message = getArguments().getString(ARG_MSG);
+        String leftButton = getArguments().getString(ARG_POS_BUTTON_STRING);
+        String rightButton = getArguments().getString(ARG_NEG_BUTTON_STRING);
+
+        Log.i(TAG, "Showing dialog with title =" + title);
+        AlertDialog.Builder builder =
+                new AlertDialog.Builder(getContext())
+                        .setPositiveButton(rightButton, this)
+                        .setNegativeButton(leftButton, this);
+
+        View content =
+                LayoutInflater.from(getContext())
+                        .inflate(R.layout.sim_warning_dialog_wifi_connectivity, null);
+
+        if (content != null) {
+            TextView dialogTitle = content.findViewById(R.id.title);
+            if (!TextUtils.isEmpty(title) && dialogTitle != null) {
+                dialogTitle.setText(title);
+                dialogTitle.setVisibility(View.VISIBLE);
+            }
+            TextView dialogMessage = content.findViewById(R.id.msg);
+            if (!TextUtils.isEmpty(message) && dialogMessage != null) {
+                dialogMessage.setText(message);
+                dialogMessage.setVisibility(View.VISIBLE);
+            }
+
+            builder.setView(content);
+        } else {
+            if (!TextUtils.isEmpty(title)) {
+                builder.setTitle(title);
+            }
+            if (!TextUtils.isEmpty(message)) {
+                builder.setMessage(message);
+            }
+        }
+
+        AlertDialog dialog = builder.create();
+        dialog.setCanceledOnTouchOutside(false);
+        return dialog;
+    }
+
+    @Override
+    public void onClick(@NonNull DialogInterface dialog, int which) {
+        Log.i(TAG, "dialog onClick =" + which);
+
+        // Positions of the buttons have been switch:
+        // negative button = left button = the button to continue
+        informCaller(which == DialogInterface.BUTTON_NEGATIVE);
+    }
+
+    @Override
+    public void onCancel(@NonNull DialogInterface dialog) {
+        informCaller(false);
+    }
+
+    private void informCaller(boolean confirmed) {
+        OnConfirmListener listener;
+        try {
+            listener = getListener(OnConfirmListener.class);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Do nothing and return.", e);
+            return;
+        }
+        if (listener == null) {
+            return;
+        }
+        listener.onConfirm(getTagInCaller(), confirmed);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java b/tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java
new file mode 100644
index 0000000..2595510
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 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 org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.telephony.SubscriptionManager;
+
+import androidx.test.core.app.ApplicationProvider;
+
+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.shadows.ShadowSubscriptionManager;
+
+@RunWith(RobolectricTestRunner.class)
+public class SubscriptionUtilRoboTest {
+    private static final int SUBID_1 = 1;
+    private static final int SUBID_2 = 2;
+
+    private Context mContext;
+    private NetworkCapabilities mNetworkCapabilities;
+    private ShadowSubscriptionManager mShadowSubscriptionManager;
+
+    @Mock
+    private ConnectivityManager mConnectivityManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mShadowSubscriptionManager = shadowOf(mContext.getSystemService(SubscriptionManager.class));
+        when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager);
+    }
+
+    @Test
+    public void isConnectedToWifiOrDifferentSubId_hasDataOnSubId2_returnTrue() {
+        addNetworkTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        mShadowSubscriptionManager.setActiveDataSubscriptionId(SUBID_2);
+
+        assertTrue(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1));
+    }
+
+    @Test
+    public void isConnectedToWifiOrDifferentSubId_hasDataOnSubId1_returnFalse() {
+        addNetworkTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        mShadowSubscriptionManager.setActiveDataSubscriptionId(SUBID_1);
+
+        assertFalse(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1));
+    }
+
+    private void addNetworkTransportType(int networkType) {
+        mNetworkCapabilities =
+                new NetworkCapabilities.Builder().addTransportType(networkType).build();
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
index 587e734..3b9ac9d 100644
--- a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
+++ b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
@@ -18,9 +18,13 @@
 
 import static com.android.settings.network.SubscriptionUtil.KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME;
 import static com.android.settings.network.SubscriptionUtil.SUB_ID;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -30,6 +34,8 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -61,13 +67,15 @@
     private static final CharSequence CARRIER_2 = "carrier2";
 
     private Context mContext;
+    private NetworkCapabilities mNetworkCapabilities;
+
     @Mock
     private SubscriptionManager mSubMgr;
     @Mock
     private TelephonyManager mTelMgr;
     @Mock
     private Resources mResources;
-
+    @Mock private ConnectivityManager mConnectivityManager;
 
     @Before
     public void setUp() {
@@ -75,6 +83,7 @@
         mContext = spy(ApplicationProvider.getApplicationContext());
         when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubMgr);
         when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelMgr);
+        when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager);
         when(mTelMgr.getUiccSlotsInfo()).thenReturn(null);
     }
 
@@ -588,4 +597,24 @@
 
         assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isFalse();
     }
+
+    @Test
+    public void isConnectedToWifiOrDifferentSubId_hasWiFi_returnTrue() {
+        addNetworkTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+
+        assertTrue(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1));
+    }
+
+    @Test
+    public void isConnectedToWifiOrDifferentSubId_noData_and_noWiFi_returnFalse() {
+        addNetworkTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH);
+
+        assertFalse(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1));
+    }
+
+    private void addNetworkTransportType(int networkType) {
+        mNetworkCapabilities =
+                new NetworkCapabilities.Builder().addTransportType(networkType).build();
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+    }
 }