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

Test: make
Bug: 316419093
Change-Id: Iaed54afa7cfd20c1dd6adbd4d50f54cab3da095d
This commit is contained in:
Rafael Higuera Silva
2024-02-14 19:36:16 +00:00
parent 74f8cb6eeb
commit 736df6d2ef
8 changed files with 450 additions and 2 deletions

View File

@@ -818,6 +818,10 @@
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/> android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/>
<activity android:name=".network.telephony.EuiccRacConnectivityDialogActivity"
android:exported="false"
android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/>
<activity <activity
android:name="Settings$TetherSettingsActivity" android:name="Settings$TetherSettingsActivity"
android:label="@string/tether_settings_title_all" android:label="@string/tether_settings_title_all"

View File

@@ -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>

View File

@@ -11660,6 +11660,16 @@
<!-- Body text of error message indicating the device could not erase the SIM due to an error. [CHAR_LIMIT=NONE] --> <!-- 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> <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] --> <!-- Title for Network connection request Dialog [CHAR LIMIT=60] -->
<string name="network_connection_request_dialog_title">Connect to device</string> <string name="network_connection_request_dialog_title">Connect to device</string>
<!-- Summary for Network connection request Dialog [CHAR LIMIT=NONE] --> <!-- Summary for Network connection request Dialog [CHAR LIMIT=NONE] -->

View File

@@ -17,13 +17,15 @@
package com.android.settings.network; package com.android.settings.network;
import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; 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.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
import static com.android.internal.util.CollectionUtils.emptyIfNull; import static com.android.internal.util.CollectionUtils.emptyIfNull;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.provider.Settings; import android.provider.Settings;
import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils;
@@ -560,6 +562,7 @@ public class SubscriptionUtil {
Log.i(TAG, "Unable to delete subscription due to invalid subscription ID."); Log.i(TAG, "Unable to delete subscription due to invalid subscription ID.");
return; return;
} }
// TODO(b/325693582): Add verification if carrier is RAC and logic for new dialog
context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId)); context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
} }
@@ -832,4 +835,29 @@ public class SubscriptionUtil {
} }
return true; 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;
}
} }

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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.KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME;
import static com.android.settings.network.SubscriptionUtil.SUB_ID; import static com.android.settings.network.SubscriptionUtil.SUB_ID;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@@ -30,6 +34,8 @@ import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
@@ -61,13 +67,15 @@ public class SubscriptionUtilTest {
private static final CharSequence CARRIER_2 = "carrier2"; private static final CharSequence CARRIER_2 = "carrier2";
private Context mContext; private Context mContext;
private NetworkCapabilities mNetworkCapabilities;
@Mock @Mock
private SubscriptionManager mSubMgr; private SubscriptionManager mSubMgr;
@Mock @Mock
private TelephonyManager mTelMgr; private TelephonyManager mTelMgr;
@Mock @Mock
private Resources mResources; private Resources mResources;
@Mock private ConnectivityManager mConnectivityManager;
@Before @Before
public void setUp() { public void setUp() {
@@ -75,6 +83,7 @@ public class SubscriptionUtilTest {
mContext = spy(ApplicationProvider.getApplicationContext()); mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubMgr); when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubMgr);
when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelMgr); when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelMgr);
when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager);
when(mTelMgr.getUiccSlotsInfo()).thenReturn(null); when(mTelMgr.getUiccSlotsInfo()).thenReturn(null);
} }
@@ -588,4 +597,24 @@ public class SubscriptionUtilTest {
assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isFalse(); 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);
}
} }