From 736df6d2ef7e4659a33e7be52672a25e4f257471 Mon Sep 17 00:00:00 2001 From: Rafael Higuera Silva Date: Wed, 14 Feb 2024 19:36:16 +0000 Subject: [PATCH] Add new dialogue when user is going to delete sim that use RAC. Test: make Bug: 316419093 Change-Id: Iaed54afa7cfd20c1dd6adbd4d50f54cab3da095d --- AndroidManifest.xml | 4 + .../sim_warning_dialog_wifi_connectivity.xml | 52 ++++++ res/values/strings.xml | 10 ++ .../settings/network/SubscriptionUtil.java | 30 +++- .../EuiccRacConnectivityDialogActivity.java | 95 +++++++++++ .../telephony/WarningDialogFragment.java | 148 ++++++++++++++++++ .../network/SubscriptionUtilRoboTest.java | 82 ++++++++++ .../network/SubscriptionUtilTest.java | 31 +++- 8 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 res/layout/sim_warning_dialog_wifi_connectivity.xml create mode 100644 src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java create mode 100644 src/com/android/settings/network/telephony/WarningDialogFragment.java create mode 100644 tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6e24863e27d..00d9f08356f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -818,6 +818,10 @@ android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/> + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 012ddc3498b..db6e30c6dc6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -11660,6 +11660,16 @@ Something went wrong and this eSIM wasn\'t erased.\n\nRestart your device and try again. + + + Connect to Wi\u2011Fi before erasing + + This makes it easier to use your eSIM again in the future without needing to contact your carrier + + Erase anyway + + OK + Connect to device diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index 84e4e7543fd..2498ec9407a 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 @@ public class SubscriptionUtil { 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 @@ public class SubscriptionUtil { } 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 00000000000..cb4ab18dc67 --- /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 00000000000..58bc1dafb3c --- /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 void show( + FragmentActivity activity, + Class 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 00000000000..2595510a540 --- /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 587e7349ad8..3b9ac9dea86 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 @@ package com.android.settings.network; 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 static org.mockito.Mockito.when; 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 @@ public class SubscriptionUtilTest { 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 @@ public class SubscriptionUtilTest { 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 @@ public class SubscriptionUtilTest { 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); + } }