Add checkbox for erase esim under network reset screen

This CL add a checkbox letting user decide when they reset the network
configurations under System > Reset options > Reset Wi-Fi, mobile &
Bluetooth, whether they want to reset eSIM together or not. If the user
choose to reset eSIM togather, EuiccManager#eraseSubscriptions will be
called and all the eSIM profiles will be removed.

Bug: 62961867
Test: E2E & make RunSettingsRoboTests
Change-Id: I533756b12c0474e8e58cc6fe60a38c119365cee2
This commit is contained in:
Qingxi Li
2018-01-05 10:52:42 -08:00
parent 18a9d7bad0
commit 0caad2f3ba
9 changed files with 313 additions and 9 deletions

View File

@@ -93,7 +93,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:paddingEnd="8dp" android:paddingEnd="@dimen/reset_checkbox_padding_end"
android:focusable="false" android:focusable="false"
android:clickable="false" android:clickable="false"
android:duplicateParentState="true" /> android:duplicateParentState="true" />
@@ -104,14 +104,14 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="12dp" android:paddingTop="@dimen/reset_checkbox_title_padding_top"
android:textSize="18sp" android:textSize="@dimen/reset_checkbox_title_text_size"
android:text="@string/erase_external_storage" /> android:text="@string/erase_external_storage" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="4sp" android:paddingTop="@dimen/reset_checkbox_summary_padding_top"
android:textSize="14sp" android:textSize="@dimen/reset_checkbox_summary_text_size"
android:text="@string/erase_external_storage_description" /> android:text="@string/erase_external_storage_description" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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="horizontal"
android:focusable="true"
android:clickable="true"
android:visibility="gone">
<CheckBox android:id="@+id/erase_esim"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingEnd="@dimen/reset_checkbox_padding_end"
android:focusable="false"
android:clickable="false"
android:duplicateParentState="true" />
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView android:id="@+id/erase_esim_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="@dimen/reset_checkbox_title_padding_top"
android:textSize="@dimen/reset_checkbox_title_text_size" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="@dimen/reset_checkbox_summary_padding_top"
android:textSize="@dimen/reset_checkbox_summary_text_size"
android:text="@string/reset_esim_desc" />
</LinearLayout>
</LinearLayout>

View File

@@ -14,7 +14,8 @@
limitations under the License. limitations under the License.
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" > android:orientation="vertical" >
@@ -27,7 +28,8 @@
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:layout_weight="1"> android:layout_weight="1">
<LinearLayout android:layout_width="match_parent" <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
@@ -38,6 +40,11 @@
android:textDirection="locale" android:textDirection="locale"
android:text="@string/reset_network_desc" /> android:text="@string/reset_network_desc" />
<include layout="@layout/reset_esim_checkbox"
android:id="@+id/erase_esim_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -311,4 +311,11 @@
<dimen name="suggestion_card_title_padding_bottom_one_card">6dp</dimen> <dimen name="suggestion_card_title_padding_bottom_one_card">6dp</dimen>
<dimen name="suggestion_card_title_padding_bottom_multiple_cards">8dp</dimen> <dimen name="suggestion_card_title_padding_bottom_multiple_cards">8dp</dimen>
<!-- Padding for the reset screens -->
<dimen name="reset_checkbox_padding_end">8dp</dimen>
<dimen name="reset_checkbox_title_padding_top">12dp</dimen>
<dimen name="reset_checkbox_summary_padding_top">4dp</dimen>
<dimen name="reset_checkbox_title_text_size">18sp</dimen>
<dimen name="reset_checkbox_summary_text_size">14sp</dimen>
</resources> </resources>

View File

@@ -3213,6 +3213,10 @@
<string name="reset_network_title">Reset Wi-Fi, mobile &amp; Bluetooth</string> <string name="reset_network_title">Reset Wi-Fi, mobile &amp; Bluetooth</string>
<!-- SD card & phone storage settings screen, message on screen after user selects Reset network settings [CHAR LIMIT=NONE] --> <!-- SD card & phone storage settings screen, message on screen after user selects Reset network settings [CHAR LIMIT=NONE] -->
<string name="reset_network_desc">This will reset all network settings, including:\n\n<li>Wi\u2011Fi</li>\n<li>Mobile data</li>\n<li>Bluetooth</li>"</string> <string name="reset_network_desc">This will reset all network settings, including:\n\n<li>Wi\u2011Fi</li>\n<li>Mobile data</li>\n<li>Bluetooth</li>"</string>
<!-- SD card & phone storage settings screen, title for the checkbox to let user decide whether erase eSIM data together [CHAR LIMIT=NONE] -->
<string name="reset_esim_title">Also reset eSIMs</string>
<!-- SD card & phone storage settings screen, message for the checkbox to let user decide whether erase eSIM data together [CHAR LIMIT=NONE] -->
<string name="reset_esim_desc">Erase all eSIMs on the phone. You\u2019ll have to contract your carrier to redownload your eSIMs. This will not cancel your mobile service plan.</string>
<!-- SD card & phone storage settings screen, button on screen after user selects Reset network settings --> <!-- SD card & phone storage settings screen, button on screen after user selects Reset network settings -->
<string name="reset_network_button_text">Reset settings</string> <string name="reset_network_button_text">Reset settings</string>
<!-- SD card & phone storage settings screen, message on screen after user selects Reset settings button --> <!-- SD card & phone storage settings screen, message on screen after user selects Reset settings button -->
@@ -3225,6 +3229,10 @@
<string name="network_reset_not_available">Network reset is not available for this user</string> <string name="network_reset_not_available">Network reset is not available for this user</string>
<!-- Reset settings complete toast text [CHAR LIMIT=75] --> <!-- Reset settings complete toast text [CHAR LIMIT=75] -->
<string name="reset_network_complete_toast">Network settings have been reset</string> <string name="reset_network_complete_toast">Network settings have been reset</string>
<!-- Title of the error message shown when error happens during erase eSIM data [CHAR LIMIT=NONE] -->
<string name="reset_esim_error_title">Cant\u2019t reset eSIMs</string>
<!-- Message of the error message shown when error happens during erase eSIM data [CHAR LIMIT=NONE] -->
<string name="reset_esim_error_msg">The eSIMs can\u2019tt be reset due to an error.</string>
<!-- Master Clear --> <!-- Master Clear -->
<!-- Button title to factory data reset the entire device --> <!-- Button title to factory data reset the entire device -->

View File

@@ -18,20 +18,28 @@ package com.android.settings;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Activity; import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager;
import android.telephony.euicc.EuiccManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneConstants;
@@ -64,6 +72,8 @@ public class ResetNetwork extends InstrumentedPreferenceFragment {
private View mContentView; private View mContentView;
private Spinner mSubscriptionSpinner; private Spinner mSubscriptionSpinner;
private Button mInitiateButton; private Button mInitiateButton;
private View mEsimContainer;
private CheckBox mEsimCheckbox;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -107,6 +117,7 @@ public class ResetNetwork extends InstrumentedPreferenceFragment {
SubscriptionInfo subscription = mSubscriptions.get(selectedIndex); SubscriptionInfo subscription = mSubscriptions.get(selectedIndex);
args.putInt(PhoneConstants.SUBSCRIPTION_KEY, subscription.getSubscriptionId()); args.putInt(PhoneConstants.SUBSCRIPTION_KEY, subscription.getSubscriptionId());
} }
args.putBoolean(MasterClear.ERASE_ESIMS_EXTRA, mEsimCheckbox.isChecked());
((SettingsActivity) getActivity()).startPreferencePanel( ((SettingsActivity) getActivity()).startPreferencePanel(
this, ResetNetworkConfirm.class.getName(), this, ResetNetworkConfirm.class.getName(),
args, R.string.reset_network_confirm_title, null, null, 0); args, R.string.reset_network_confirm_title, null, null, 0);
@@ -141,6 +152,8 @@ public class ResetNetwork extends InstrumentedPreferenceFragment {
*/ */
private void establishInitialState() { private void establishInitialState() {
mSubscriptionSpinner = (Spinner) mContentView.findViewById(R.id.reset_network_subscription); mSubscriptionSpinner = (Spinner) mContentView.findViewById(R.id.reset_network_subscription);
mEsimContainer = mContentView.findViewById(R.id.erase_esim_container);
mEsimCheckbox = mContentView.findViewById(R.id.erase_esim);
mSubscriptions = SubscriptionManager.from(getActivity()).getActiveSubscriptionInfoList(); mSubscriptions = SubscriptionManager.from(getActivity()).getActiveSubscriptionInfoList();
if (mSubscriptions != null && mSubscriptions.size() > 0) { if (mSubscriptions != null && mSubscriptions.size() > 0) {
@@ -192,6 +205,30 @@ public class ResetNetwork extends InstrumentedPreferenceFragment {
} }
mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_reset_network); mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_reset_network);
mInitiateButton.setOnClickListener(mInitiateListener); mInitiateButton.setOnClickListener(mInitiateListener);
if (showEuiccSettings(getContext())) {
mEsimContainer.setVisibility(View.VISIBLE);
TextView title = mContentView.findViewById(R.id.erase_esim_title);
title.setText(R.string.reset_esim_title);
mEsimContainer.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mEsimCheckbox.toggle();
}
});
} else {
mEsimCheckbox.setChecked(false /* checked */);
}
}
private boolean showEuiccSettings(Context context) {
EuiccManager euiccManager =
(EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
if (!euiccManager.isEnabled()) {
return false;
}
ContentResolver resolver = context.getContentResolver();
return Settings.Global.getInt(resolver, Global.EUICC_PROVISIONED, 0) != 0
|| Settings.Global.getInt(resolver, Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
} }
@Override @Override

View File

@@ -16,6 +16,7 @@
package com.android.settings; package com.android.settings;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothManager;
import android.content.ContentResolver; import android.content.ContentResolver;
@@ -24,9 +25,12 @@ import android.net.ConnectivityManager;
import android.net.NetworkPolicyManager; import android.net.NetworkPolicyManager;
import android.net.Uri; import android.net.Uri;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.RecoverySystem;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -39,6 +43,7 @@ import com.android.ims.ImsManager;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneConstants;
import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.wrapper.RecoverySystemWrapper;
import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -57,6 +62,43 @@ public class ResetNetworkConfirm extends InstrumentedPreferenceFragment {
private View mContentView; private View mContentView;
private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@VisibleForTesting boolean mEraseEsim;
@VisibleForTesting EraseEsimAsyncTask mEraseEsimTask;
@VisibleForTesting static RecoverySystemWrapper mRecoverySystem;
/**
* Async task used to erase all the eSIM profiles from the phone. If error happens during
* erasing eSIM profiles or timeout, an error msg is shown.
*/
private static class EraseEsimAsyncTask extends AsyncTask<Void, Void, Boolean> {
private final Context mContext;
private final String mPackageName;
EraseEsimAsyncTask(Context context, String packageName) {
mContext = context;
mPackageName = packageName;
}
@Override
protected Boolean doInBackground(Void... params) {
return mRecoverySystem.wipeEuiccData(
mContext, true /* isWipeEuicc */, mPackageName);
}
@Override
protected void onPostExecute(Boolean succeeded) {
if (succeeded) {
Toast.makeText(mContext, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT)
.show();
} else {
new AlertDialog.Builder(mContext)
.setTitle(R.string.reset_esim_error_title)
.setMessage(R.string.reset_esim_error_msg)
.setPositiveButton(android.R.string.ok, null /* listener */)
.show();
}
}
}
/** /**
* The user has gone through the multiple confirmation, so now we go ahead * The user has gone through the multiple confirmation, so now we go ahead
@@ -69,7 +111,8 @@ public class ResetNetworkConfirm extends InstrumentedPreferenceFragment {
if (Utils.isMonkeyRunning()) { if (Utils.isMonkeyRunning()) {
return; return;
} }
// TODO maybe show a progress dialog if this ends up taking a while // TODO maybe show a progress screen if this ends up taking a while and won't let user
// go back until the tasks finished.
Context context = getActivity(); Context context = getActivity();
ConnectivityManager connectivityManager = (ConnectivityManager) ConnectivityManager connectivityManager = (ConnectivityManager)
@@ -108,11 +151,20 @@ public class ResetNetworkConfirm extends InstrumentedPreferenceFragment {
ImsManager.factoryReset(context); ImsManager.factoryReset(context);
restoreDefaultApn(context); restoreDefaultApn(context);
esimFactoryReset(context, context.getPackageName());
}
};
@VisibleForTesting
void esimFactoryReset(Context context, String packageName) {
if (mEraseEsim) {
mEraseEsimTask = new EraseEsimAsyncTask(context, packageName);
mEraseEsimTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
Toast.makeText(context, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT) Toast.makeText(context, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT)
.show(); .show();
} }
}; }
/** /**
* Restore APN settings to default. * Restore APN settings to default.
@@ -163,6 +215,16 @@ public class ResetNetworkConfirm extends InstrumentedPreferenceFragment {
if (args != null) { if (args != null) {
mSubId = args.getInt(PhoneConstants.SUBSCRIPTION_KEY, mSubId = args.getInt(PhoneConstants.SUBSCRIPTION_KEY,
SubscriptionManager.INVALID_SUBSCRIPTION_ID); SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mEraseEsim = args.getBoolean(MasterClear.ERASE_ESIMS_EXTRA);
}
mRecoverySystem = new RecoverySystemWrapper();
}
@Override
public void onDestroy() {
if (mEraseEsimTask != null) {
mEraseEsimTask.cancel(true /* mayInterruptIfRunning */);
mEraseEsimTask = null;
} }
} }

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2018 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.wrapper;
import android.content.Context;
import android.os.RecoverySystem;
/**
* This class replicates a subset of the {@link RecoverySystem}.
* The interface exists so that we can use a thin wrapper around the RecoverySystem in
* production code and a mock in tests.
*/
public class RecoverySystemWrapper {
/**
* Returns whether wipe Euicc data successfully or not.
*
* @param isWipeEuicc whether we want to wipe Euicc data or not
* @param packageName the package name of the caller app.
*/
public boolean wipeEuiccData(
Context context, final boolean isWipeEuicc, final String packageName) {
return RecoverySystem.wipeEuiccData(context, isWipeEuicc, packageName);
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2018 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;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.content.Context;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.wrapper.RecoverySystemWrapper;
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;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(
manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION
)
public class ResetNetworkConfirmTest {
private Activity mActivity;
@Mock
private ResetNetworkConfirm mResetNetworkConfirm;
@Mock
private RecoverySystemWrapper mRecoverySystem;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mResetNetworkConfirm = spy(new ResetNetworkConfirm());
mRecoverySystem = spy(new RecoverySystemWrapper());
ResetNetworkConfirm.mRecoverySystem = mRecoverySystem;
mActivity = Robolectric.setupActivity(Activity.class);
}
@Test
public void testResetNetworkData_resetEsim() {
mResetNetworkConfirm.mEraseEsim = true;
doReturn(true)
.when(mRecoverySystem).wipeEuiccData(any(Context.class), anyBoolean(), anyString());
mResetNetworkConfirm.esimFactoryReset(mActivity, "" /* packageName */);
try {
// Waiting the Async task finished
Thread.sleep(10000); // 10 sec
} catch (InterruptedException ignore) {
}
Assert.assertNotNull(mResetNetworkConfirm.mEraseEsimTask);
verify(mRecoverySystem).wipeEuiccData(any(Context.class), anyBoolean(), anyString());
}
@Test
public void testResetNetworkData_notResetEsim() {
mResetNetworkConfirm.mEraseEsim = false;
mResetNetworkConfirm.esimFactoryReset(mActivity, "" /* packageName */);
Assert.assertNull(mResetNetworkConfirm.mEraseEsimTask);
verify(mRecoverySystem, never())
.wipeEuiccData(any(Context.class), anyBoolean(), anyString());
}
}