Merge "Add checkbox for erase esim under network reset screen"

This commit is contained in:
Qingxi Li
2018-01-20 06:20:02 +00:00
committed by Android (Google) Code Review
9 changed files with 313 additions and 9 deletions

View File

@@ -93,7 +93,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingEnd="8dp"
android:paddingEnd="@dimen/reset_checkbox_padding_end"
android:focusable="false"
android:clickable="false"
android:duplicateParentState="true" />
@@ -104,14 +104,14 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:textSize="18sp"
android:paddingTop="@dimen/reset_checkbox_title_padding_top"
android:textSize="@dimen/reset_checkbox_title_text_size"
android:text="@string/erase_external_storage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="4sp"
android:textSize="14sp"
android:paddingTop="@dimen/reset_checkbox_summary_padding_top"
android:textSize="@dimen/reset_checkbox_summary_text_size"
android:text="@string/erase_external_storage_description" />
</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.
-->
<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_height="match_parent"
android:orientation="vertical" >
@@ -27,7 +28,8 @@
android:layout_marginTop="12dp"
android:layout_weight="1">
<LinearLayout android:layout_width="match_parent"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
@@ -38,6 +40,11 @@
android:textDirection="locale"
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>
</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_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>

View File

@@ -3213,6 +3213,10 @@
<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] -->
<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 -->
<string name="reset_network_button_text">Reset settings</string>
<!-- 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>
<!-- Reset settings complete toast text [CHAR LIMIT=75] -->
<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 -->
<!-- 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.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.euicc.EuiccManager;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Spinner;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.telephony.PhoneConstants;
@@ -64,6 +72,8 @@ public class ResetNetwork extends InstrumentedPreferenceFragment {
private View mContentView;
private Spinner mSubscriptionSpinner;
private Button mInitiateButton;
private View mEsimContainer;
private CheckBox mEsimCheckbox;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -107,6 +117,7 @@ public class ResetNetwork extends InstrumentedPreferenceFragment {
SubscriptionInfo subscription = mSubscriptions.get(selectedIndex);
args.putInt(PhoneConstants.SUBSCRIPTION_KEY, subscription.getSubscriptionId());
}
args.putBoolean(MasterClear.ERASE_ESIMS_EXTRA, mEsimCheckbox.isChecked());
((SettingsActivity) getActivity()).startPreferencePanel(
this, ResetNetworkConfirm.class.getName(),
args, R.string.reset_network_confirm_title, null, null, 0);
@@ -141,6 +152,8 @@ public class ResetNetwork extends InstrumentedPreferenceFragment {
*/
private void establishInitialState() {
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();
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.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

View File

@@ -16,6 +16,7 @@
package com.android.settings;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.ContentResolver;
@@ -24,9 +25,12 @@ import android.net.ConnectivityManager;
import android.net.NetworkPolicyManager;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RecoverySystem;
import android.os.UserHandle;
import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
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.telephony.PhoneConstants;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.wrapper.RecoverySystemWrapper;
import com.android.settingslib.RestrictedLockUtils;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -57,6 +62,43 @@ public class ResetNetworkConfirm extends InstrumentedPreferenceFragment {
private View mContentView;
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
@@ -69,7 +111,8 @@ public class ResetNetworkConfirm extends InstrumentedPreferenceFragment {
if (Utils.isMonkeyRunning()) {
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();
ConnectivityManager connectivityManager = (ConnectivityManager)
@@ -108,11 +151,20 @@ public class ResetNetworkConfirm extends InstrumentedPreferenceFragment {
ImsManager.factoryReset(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)
.show();
}
};
}
/**
* Restore APN settings to default.
@@ -163,6 +215,16 @@ public class ResetNetworkConfirm extends InstrumentedPreferenceFragment {
if (args != null) {
mSubId = args.getInt(PhoneConstants.SUBSCRIPTION_KEY,
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());
}
}