Fix unable to erase eSIM

Before this change,
- eSIM will be erased twice, one with result callback and one without
  result callback.
- During reset, ResetNetworkConfirm could interrupted by subscription
  invalid event, which happens during reset.

After this change,
- eSIM will be erased only once, result callback is registered
  separately.
- Explicit exit the page when reset finish, and ignore the subscription
  invalid event after reset started.

Bug: 328293508
Flag: EXEMPT bug fix
Test: manual - dry run the reset
Test: ResetNetworkConfirmTest
Change-Id: I51395a556b1c8775192d5897a87f13046c042578
This commit is contained in:
Chaohui Wang
2024-06-24 16:52:01 +08:00
parent edc72a9b0f
commit b70c805717
8 changed files with 312 additions and 649 deletions

View File

@@ -52,6 +52,7 @@ import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity; import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmLockPattern; import com.android.settings.password.ConfirmLockPattern;
import com.android.settings.system.reset.ResetNetworkConfirm;
import com.android.settingslib.development.DevelopmentSettingsEnabler; import com.android.settingslib.development.DevelopmentSettingsEnabler;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -1,247 +0,0 @@
/*
* Copyright (C) 2015 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 android.app.Activity;
import android.app.ProgressDialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Looper;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.network.ResetNetworkOperationBuilder;
import com.android.settings.network.ResetNetworkRestrictionViewBuilder;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Confirm and execute a reset of the network settings to a clean "just out of the box"
* state. Multiple confirmations are required: first, a general "are you sure
* you want to do this?" prompt, followed by a keyguard pattern trace if the user
* has defined one, followed by a final strongly-worded "THIS WILL RESET EVERYTHING"
* prompt. If at any time the phone is allowed to go to sleep, is
* locked, et cetera, then the confirmation sequence is abandoned.
*
* This is the confirmation screen.
*/
public class ResetNetworkConfirm extends InstrumentedFragment {
private static final String TAG = "ResetNetworkConfirm";
@VisibleForTesting View mContentView;
@VisibleForTesting ResetNetworkTask mResetNetworkTask;
@VisibleForTesting Activity mActivity;
@VisibleForTesting ResetNetworkRequest mResetNetworkRequest;
private ProgressDialog mProgressDialog;
private AlertDialog mAlertDialog;
@VisibleForTesting ResetSubscriptionContract mResetSubscriptionContract;
private OnSubscriptionsChangedListener mSubscriptionsChangedListener;
/**
* Async task used to do all reset task. If error happens during
* erasing eSIM profiles or timeout, an error msg is shown.
*/
private class ResetNetworkTask extends AsyncTask<Void, Void, Boolean> {
private static final String TAG = "ResetNetworkTask";
private final Context mContext;
ResetNetworkTask(Context context) {
mContext = context;
}
@Override
protected Boolean doInBackground(Void... params) {
final AtomicBoolean resetEsimSuccess = new AtomicBoolean(true);
String resetEsimPackageName = mResetNetworkRequest.getResetEsimPackageName();
ResetNetworkOperationBuilder builder = mResetNetworkRequest
.toResetNetworkOperationBuilder(mContext, Looper.getMainLooper());
if (resetEsimPackageName != null) {
// Override reset eSIM option for the result of reset operation
builder = builder.resetEsim(resetEsimPackageName,
success -> { resetEsimSuccess.set(success); }
);
}
builder.build().run();
boolean isResetSucceed = resetEsimSuccess.get();
Log.d(TAG, "network factoryReset complete. succeeded: "
+ String.valueOf(isResetSucceed));
return isResetSucceed;
}
@Override
protected void onPostExecute(Boolean succeeded) {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
if (succeeded) {
Toast.makeText(mContext, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT)
.show();
} else {
mAlertDialog = 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
* and reset the network settings to its factory-default state.
*/
@VisibleForTesting
Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
@Override
public void onClick(View v) {
if (Utils.isMonkeyRunning()) {
return;
}
// abandon execution if subscription no longer active
Integer subId = mResetSubscriptionContract.getAnyMissingSubscriptionId();
if (subId != null) {
Log.w(TAG, "subId " + subId + " no longer active");
getActivity().onBackPressed();
return;
}
// Should dismiss the progress dialog firstly if it is showing
// Or not the progress dialog maybe not dismissed in fast clicking.
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
mProgressDialog = getProgressDialog(mActivity);
mProgressDialog.show();
mResetNetworkTask = new ResetNetworkTask(mActivity);
mResetNetworkTask.execute();
}
};
private ProgressDialog getProgressDialog(Context context) {
final ProgressDialog progressDialog = new ProgressDialog(context);
progressDialog.setIndeterminate(true);
progressDialog.setCancelable(false);
progressDialog.setMessage(
context.getString(R.string.main_clear_progress_text));
return progressDialog;
}
/**
* Configure the UI for the final confirmation interaction
*/
private void establishFinalConfirmationState() {
mContentView.findViewById(R.id.execute_reset_network)
.setOnClickListener(mFinalClickListener);
}
@VisibleForTesting
void setSubtitle() {
if (mResetNetworkRequest.getResetEsimPackageName() != null) {
((TextView) mContentView.findViewById(R.id.reset_network_confirm))
.setText(R.string.reset_network_final_desc_esim);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = (new ResetNetworkRestrictionViewBuilder(mActivity)).build();
if (view != null) {
mResetSubscriptionContract.close();
Log.w(TAG, "Access deny.");
return view;
}
mContentView = inflater.inflate(R.layout.reset_network_confirm, null);
establishFinalConfirmationState();
setSubtitle();
return mContentView;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args == null) {
args = savedInstanceState;
}
mResetNetworkRequest = new ResetNetworkRequest(args);
mActivity = getActivity();
mResetSubscriptionContract = new ResetSubscriptionContract(getContext(),
mResetNetworkRequest) {
@Override
public void onSubscriptionInactive(int subscriptionId) {
// close UI if subscription no longer active
Log.w(TAG, "subId " + subscriptionId + " no longer active.");
getActivity().onBackPressed();
}
};
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mResetNetworkRequest.writeIntoBundle(outState);
}
@Override
public void onDestroy() {
if (mResetNetworkTask != null) {
mResetNetworkTask.cancel(true /* mayInterruptIfRunning */);
mResetNetworkTask = null;
}
if (mResetSubscriptionContract != null) {
mResetSubscriptionContract.close();
mResetSubscriptionContract = null;
}
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
if (mAlertDialog != null) {
mAlertDialog.dismiss();
}
super.onDestroy();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.RESET_NETWORK_CONFIRM;
}
}

View File

@@ -1,157 +0,0 @@
/*
* Copyright (C) 2022 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 android.content.Context;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.IntStream;
/**
* A Class monitoring the availability of subscription IDs provided within reset request.
*
* This is to detect the situation when user changing SIM card during the presenting of
* confirmation UI.
*/
public class ResetSubscriptionContract implements AutoCloseable {
private static final String TAG = "ResetSubscriptionContract";
private final Context mContext;
private ExecutorService mExecutorService;
private final int [] mResetSubscriptionIds;
@VisibleForTesting
protected OnSubscriptionsChangedListener mSubscriptionsChangedListener;
private AtomicBoolean mSubscriptionsUpdateNotify = new AtomicBoolean();
/**
* Constructor
* @param context Context
* @param resetRequest the request object for perform network reset operation.
*/
public ResetSubscriptionContract(Context context, ResetNetworkRequest resetRequest) {
mContext = context;
// Only keeps specific subscription ID required to perform reset operation
IntStream subIdStream = IntStream.of(
resetRequest.getResetTelephonyAndNetworkPolicyManager(),
resetRequest.getResetApnSubId(), resetRequest.getResetImsSubId());
mResetSubscriptionIds = subIdStream.sorted().distinct()
.filter(id -> SubscriptionManager.isUsableSubscriptionId(id))
.toArray();
if (mResetSubscriptionIds.length <= 0) {
return;
}
// Monitoring callback through background thread
mExecutorService = Executors.newSingleThreadExecutor();
startMonitorSubscriptionChange();
}
/**
* A method for detecting if there's any subscription under monitor no longer active.
* @return subscription ID which is no longer active.
*/
public Integer getAnyMissingSubscriptionId() {
if (mResetSubscriptionIds.length <= 0) {
return null;
}
SubscriptionManager mgr = getSubscriptionManager();
if (mgr == null) {
Log.w(TAG, "Fail to access subscription manager");
return mResetSubscriptionIds[0];
}
for (int idx = 0; idx < mResetSubscriptionIds.length; idx++) {
int subId = mResetSubscriptionIds[idx];
if (mgr.getActiveSubscriptionInfo(subId) == null) {
Log.w(TAG, "SubId " + subId + " no longer active.");
return subId;
}
}
return null;
}
/**
* Async callback when detecting if there's any subscription under monitor no longer active.
* @param subscriptionId subscription ID which is no longer active.
*/
public void onSubscriptionInactive(int subscriptionId) {}
@VisibleForTesting
protected SubscriptionManager getSubscriptionManager() {
return mContext.getSystemService(SubscriptionManager.class);
}
@VisibleForTesting
protected OnSubscriptionsChangedListener getChangeListener() {
return new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
/**
* Reducing the processing time on main UI thread through a flag.
* Once flag get into false, which means latest callback has been
* processed.
*/
mSubscriptionsUpdateNotify.set(true);
// Back to main UI thread
mContext.getMainExecutor().execute(() -> {
// Remove notifications and perform checking.
if (mSubscriptionsUpdateNotify.getAndSet(false)) {
Integer subId = getAnyMissingSubscriptionId();
if (subId != null) {
onSubscriptionInactive(subId);
}
}
});
}
};
}
private void startMonitorSubscriptionChange() {
SubscriptionManager mgr = getSubscriptionManager();
if (mgr == null) {
return;
}
// update monitor listener
mSubscriptionsChangedListener = getChangeListener();
mgr.addOnSubscriptionsChangedListener(
mExecutorService, mSubscriptionsChangedListener);
}
// Implementation of AutoCloseable
public void close() {
if (mExecutorService == null) {
return;
}
// Stop monitoring subscription change
SubscriptionManager mgr = getSubscriptionManager();
if (mgr != null) {
mgr.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
}
// Release Executor
mExecutorService.shutdownNow();
mExecutorService = null;
}
}

View File

@@ -65,6 +65,8 @@ public class ResetNetworkOperationBuilder {
private Context mContext; private Context mContext;
private List<Runnable> mResetSequence = new ArrayList<Runnable>(); private List<Runnable> mResetSequence = new ArrayList<Runnable>();
@Nullable
private Consumer<Boolean> mResetEsimResultCallback = null;
/** /**
* Constructor of builder. * Constructor of builder.
@@ -129,31 +131,32 @@ public class ResetNetworkOperationBuilder {
} }
/** /**
* Append a step of resetting E-SIM. * Append a result callback of resetting E-SIM.
* @param callerPackage package name of caller * @param resultCallback a callback dealing with result of resetting eSIM
* @return this * @return this
*/ */
public ResetNetworkOperationBuilder resetEsim(String callerPackage) { public ResetNetworkOperationBuilder resetEsimResultCallback(Consumer<Boolean> resultCallback) {
resetEsim(callerPackage, null); mResetEsimResultCallback = resultCallback;
return this; return this;
} }
/** /**
* Append a step of resetting E-SIM. * Append a step of resetting E-SIM.
* @param callerPackage package name of caller * @param callerPackage package name of caller
* @param resultCallback a Consumer<Boolean> dealing with result of resetting eSIM
* @return this * @return this
*/ */
public ResetNetworkOperationBuilder resetEsim(String callerPackage, public ResetNetworkOperationBuilder resetEsim(String callerPackage) {
Consumer<Boolean> resultCallback) {
Runnable runnable = () -> { Runnable runnable = () -> {
long startTime = SystemClock.elapsedRealtime(); long startTime = SystemClock.elapsedRealtime();
if (!DRY_RUN) { boolean wipped;
Boolean wipped = RecoverySystem.wipeEuiccData(mContext, callerPackage); if (DRY_RUN) {
if (resultCallback != null) { wipped = true;
resultCallback.accept(wipped); } else {
} wipped = RecoverySystem.wipeEuiccData(mContext, callerPackage);
}
if (mResetEsimResultCallback != null) {
mResetEsimResultCallback.accept(wipped);
} }
long endTime = SystemClock.elapsedRealtime(); long endTime = SystemClock.elapsedRealtime();

View File

@@ -0,0 +1,217 @@
/*
* 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.system.reset
import android.app.ProgressDialog
import android.app.settings.SettingsEnums
import android.os.Bundle
import android.os.Looper
import android.telephony.SubscriptionManager
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import com.android.settings.R
import com.android.settings.ResetNetworkRequest
import com.android.settings.Utils
import com.android.settings.core.InstrumentedFragment
import com.android.settings.network.ResetNetworkRestrictionViewBuilder
import com.android.settings.network.telephony.SubscriptionRepository
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* Confirm and execute a reset of the network settings to a clean "just out of the box" state.
* Multiple confirmations are required: first, a general "are you sure you want to do this?" prompt,
* followed by a keyguard pattern trace if the user has defined one, followed by a final
* strongly-worded "THIS WILL RESET EVERYTHING" prompt. If at any time the phone is allowed to go to
* sleep, is locked, et cetera, then the confirmation sequence is abandoned.
*
* This is the confirmation screen.
*/
class ResetNetworkConfirm : InstrumentedFragment() {
@VisibleForTesting lateinit var resetNetworkRequest: ResetNetworkRequest
private var progressDialog: ProgressDialog? = null
private var alertDialog: AlertDialog? = null
private var resetStarted = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate: $arguments")
resetNetworkRequest = ResetNetworkRequest(arguments)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = ResetNetworkRestrictionViewBuilder(requireActivity()).build()
if (view != null) {
Log.w(TAG, "Access deny.")
return view
}
return inflater.inflate(R.layout.reset_network_confirm, null).apply {
establishFinalConfirmationState()
setSubtitle()
}
}
/** Configure the UI for the final confirmation interaction */
private fun View.establishFinalConfirmationState() {
requireViewById<View>(R.id.execute_reset_network).setOnClickListener {
if (!Utils.isMonkeyRunning() && !resetStarted) {
resetStarted = true
viewLifecycleOwner.lifecycleScope.launch { onResetClicked() }
}
}
}
private fun View.setSubtitle() {
if (resetNetworkRequest.resetEsimPackageName != null) {
requireViewById<TextView>(R.id.reset_network_confirm)
.setText(R.string.reset_network_final_desc_esim)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
invalidSubIdFlow().collectLatestWithLifecycle(viewLifecycleOwner) { invalidSubId ->
// Reset process could triage this callback, so if reset has started, ignore the event.
if (!resetStarted) {
Log.w(TAG, "subId $invalidSubId no longer active.")
requireActivity().finish()
}
}
}
/**
* Monitor the sub ids in the request, if any sub id becomes inactive, the request is abandoned.
*/
private fun invalidSubIdFlow(): Flow<Int> {
val subIdsInRequest =
listOf(
resetNetworkRequest.resetTelephonyAndNetworkPolicyManager,
resetNetworkRequest.resetApnSubId,
resetNetworkRequest.resetImsSubId,
)
.distinct()
.filter(SubscriptionManager::isUsableSubscriptionId)
if (subIdsInRequest.isEmpty()) return emptyFlow()
return SubscriptionRepository(requireContext())
.activeSubscriptionIdListFlow()
.mapNotNull { activeSubIds -> subIdsInRequest.firstOrNull { it !in activeSubIds } }
.conflate()
.flowOn(Dispatchers.Default)
}
/**
* The user has gone through the multiple confirmation, so now we go ahead and reset the network
* settings to its factory-default state.
*/
@VisibleForTesting
suspend fun onResetClicked() {
showProgressDialog()
resetNetwork()
}
private fun showProgressDialog() {
progressDialog =
ProgressDialog(requireContext()).apply {
isIndeterminate = true
setCancelable(false)
setMessage(requireContext().getString(R.string.main_clear_progress_text))
show()
}
}
private fun dismissProgressDialog() {
progressDialog?.let { progressDialog ->
if (progressDialog.isShowing) {
progressDialog.dismiss()
}
}
}
/**
* Do all reset task.
*
* If error happens during erasing eSIM profiles or timeout, an error msg is shown.
*/
private suspend fun resetNetwork() {
var resetEsimSuccess = true
withContext(Dispatchers.Default) {
val builder =
resetNetworkRequest.toResetNetworkOperationBuilder(
requireContext(), Looper.getMainLooper())
resetNetworkRequest.resetEsimPackageName?.let { resetEsimPackageName ->
builder.resetEsim(resetEsimPackageName)
builder.resetEsimResultCallback { resetEsimSuccess = it }
}
builder.build().run()
}
Log.d(TAG, "network factoryReset complete. succeeded: $resetEsimSuccess")
onResetFinished(resetEsimSuccess)
}
private fun onResetFinished(resetEsimSuccess: Boolean) {
dismissProgressDialog()
val activity = requireActivity()
if (!resetEsimSuccess) {
alertDialog =
AlertDialog.Builder(activity)
.setTitle(R.string.reset_esim_error_title)
.setMessage(R.string.reset_esim_error_msg)
.setPositiveButton(android.R.string.ok, /* listener= */ null)
.show()
} else {
Toast.makeText(activity, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT)
.show()
}
activity.finish()
}
override fun onDestroy() {
progressDialog?.dismiss()
alertDialog?.dismiss()
super.onDestroy()
}
override fun getMetricsCategory() = SettingsEnums.RESET_NETWORK_CONFIRM
private companion object {
const val TAG = "ResetNetworkConfirm"
}
}

View File

@@ -1,124 +0,0 @@
/*
* 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 com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import android.view.LayoutInflater;
import android.widget.TextView;
import androidx.fragment.app.FragmentActivity;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowRecoverySystem;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.util.concurrent.PausedExecutorService;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPausedAsyncTask;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowRecoverySystem.class, ShadowBluetoothAdapter.class})
public class ResetNetworkConfirmTest {
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private static final String TEST_PACKAGE = "com.android.settings";
private FragmentActivity mActivity;
@Mock
private ResetNetworkConfirm mResetNetworkConfirm;
private PausedExecutorService mExecutorService;
@Before
public void setUp() {
mExecutorService = new PausedExecutorService();
ShadowPausedAsyncTask.overrideExecutor(mExecutorService);
mResetNetworkConfirm = new ResetNetworkConfirm();
mActivity = spy(Robolectric.setupActivity(FragmentActivity.class));
mResetNetworkConfirm.mActivity = mActivity;
}
@After
public void tearDown() {
ShadowRecoverySystem.reset();
}
@Test
public void testResetNetworkData_notResetEsim() {
mResetNetworkConfirm.mResetNetworkRequest =
new ResetNetworkRequest(ResetNetworkRequest.RESET_NONE);
mResetNetworkConfirm.mResetSubscriptionContract =
new ResetSubscriptionContract(mActivity,
mResetNetworkConfirm.mResetNetworkRequest) {
@Override
public void onSubscriptionInactive(int subscriptionId) {
mActivity.onBackPressed();
}
};
mResetNetworkConfirm.mFinalClickListener.onClick(null /* View */);
mExecutorService.runAll();
ShadowLooper.idleMainLooper();
assertThat(ShadowRecoverySystem.getWipeEuiccCalledCount()).isEqualTo(0);
}
@Test
public void setSubtitle_eraseEsim() {
mResetNetworkConfirm.mResetNetworkRequest =
new ResetNetworkRequest(ResetNetworkRequest.RESET_NONE);
mResetNetworkConfirm.mResetNetworkRequest.setResetEsim(TEST_PACKAGE);
mResetNetworkConfirm.mContentView =
LayoutInflater.from(mActivity).inflate(R.layout.reset_network_confirm, null);
mResetNetworkConfirm.setSubtitle();
assertThat(((TextView) mResetNetworkConfirm.mContentView
.findViewById(R.id.reset_network_confirm)).getText())
.isEqualTo(mActivity.getString(R.string.reset_network_final_desc_esim));
}
@Test
public void setSubtitle_notEraseEsim() {
mResetNetworkConfirm.mResetNetworkRequest =
new ResetNetworkRequest(ResetNetworkRequest.RESET_NONE);
mResetNetworkConfirm.mContentView =
LayoutInflater.from(mActivity).inflate(R.layout.reset_network_confirm, null);
mResetNetworkConfirm.setSubtitle();
assertThat(((TextView) mResetNetworkConfirm.mContentView
.findViewById(R.id.reset_network_confirm)).getText())
.isEqualTo(mActivity.getString(R.string.reset_network_final_desc));
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.system.reset
import android.content.Context
import android.view.LayoutInflater
import android.widget.TextView
import androidx.fragment.app.testing.launchFragment
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settings.ResetNetworkRequest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class ResetNetworkConfirmTest {
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {}
private val scenario = launchFragment<ResetNetworkConfirm>()
@Test
fun resetNetworkData_notResetEsim() {
scenario.recreate().onFragment { fragment ->
fragment.resetNetworkRequest = ResetNetworkRequest(ResetNetworkRequest.RESET_NONE)
runBlocking { fragment.onResetClicked() }
verify(context, never()).getSystemService(any())
}
}
@Test
fun setSubtitle_eraseEsim() {
scenario.onFragment { fragment ->
fragment.resetNetworkRequest =
ResetNetworkRequest(ResetNetworkRequest.RESET_NONE).apply {
setResetEsim(context.packageName)
}
val view = fragment.onCreateView(LayoutInflater.from(context), null, null)
assertThat(view.requireViewById<TextView>(R.id.reset_network_confirm).text)
.isEqualTo(context.getString(R.string.reset_network_final_desc_esim))
}
}
@Test
fun setSubtitle_notEraseEsim() {
scenario.onFragment { fragment ->
fragment.resetNetworkRequest = ResetNetworkRequest(ResetNetworkRequest.RESET_NONE)
val view = fragment.onCreateView(LayoutInflater.from(context), null, null)
assertThat(view.requireViewById<TextView>(R.id.reset_network_confirm).text)
.isEqualTo(context.getString(R.string.reset_network_final_desc))
}
}
}

View File

@@ -1,109 +0,0 @@
/*
* Copyright (C) 2022 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.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.os.Bundle;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class ResetSubscriptionContractTest {
private static final int SUB_ID_1 = 3;
private static final int SUB_ID_2 = 8;
@Mock
private SubscriptionManager mSubscriptionManager;
@Mock
private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
@Mock
private SubscriptionInfo mSubscriptionInfo1;
@Mock
private SubscriptionInfo mSubscriptionInfo2;
private Context mContext;
private ResetNetworkRequest mRequestArgs;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(ApplicationProvider.getApplicationContext());
mRequestArgs = new ResetNetworkRequest(new Bundle());
}
private ResetSubscriptionContract createTestObject() {
return new ResetSubscriptionContract(mContext, mRequestArgs) {
@Override
protected SubscriptionManager getSubscriptionManager() {
return mSubscriptionManager;
}
@Override
protected OnSubscriptionsChangedListener getChangeListener() {
return mOnSubscriptionsChangedListener;
}
};
}
@Test
public void getAnyMissingSubscriptionId_returnNull_whenNoSubscriptionChange() {
mRequestArgs.setResetTelephonyAndNetworkPolicyManager(SUB_ID_1);
doReturn(mSubscriptionInfo1).when(mSubscriptionManager)
.getActiveSubscriptionInfo(SUB_ID_1);
mRequestArgs.setResetApn(SUB_ID_2);
doReturn(mSubscriptionInfo2).when(mSubscriptionManager)
.getActiveSubscriptionInfo(SUB_ID_2);
ResetSubscriptionContract target = createTestObject();
verify(mSubscriptionManager).addOnSubscriptionsChangedListener(any(), any());
assertNull(target.getAnyMissingSubscriptionId());
}
@Test
public void getAnyMissingSubscriptionId_returnSubId_whenSubscriptionNotActive() {
mRequestArgs.setResetTelephonyAndNetworkPolicyManager(SUB_ID_1);
doReturn(mSubscriptionInfo1).when(mSubscriptionManager)
.getActiveSubscriptionInfo(SUB_ID_1);
mRequestArgs.setResetApn(SUB_ID_2);
doReturn(null).when(mSubscriptionManager)
.getActiveSubscriptionInfo(SUB_ID_2);
ResetSubscriptionContract target = createTestObject();
verify(mSubscriptionManager).addOnSubscriptionsChangedListener(any(), any());
assertEquals(target.getAnyMissingSubscriptionId(), new Integer(SUB_ID_2));
}
}