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:
@@ -52,6 +52,7 @@ import com.android.settings.network.SubscriptionUtil;
|
||||
import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
|
||||
import com.android.settings.password.ChooseLockSettingsHelper;
|
||||
import com.android.settings.password.ConfirmLockPattern;
|
||||
import com.android.settings.system.reset.ResetNetworkConfirm;
|
||||
import com.android.settingslib.development.DevelopmentSettingsEnabler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -65,6 +65,8 @@ public class ResetNetworkOperationBuilder {
|
||||
|
||||
private Context mContext;
|
||||
private List<Runnable> mResetSequence = new ArrayList<Runnable>();
|
||||
@Nullable
|
||||
private Consumer<Boolean> mResetEsimResultCallback = null;
|
||||
|
||||
/**
|
||||
* Constructor of builder.
|
||||
@@ -129,31 +131,32 @@ public class ResetNetworkOperationBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a step of resetting E-SIM.
|
||||
* @param callerPackage package name of caller
|
||||
* Append a result callback of resetting E-SIM.
|
||||
* @param resultCallback a callback dealing with result of resetting eSIM
|
||||
* @return this
|
||||
*/
|
||||
public ResetNetworkOperationBuilder resetEsim(String callerPackage) {
|
||||
resetEsim(callerPackage, null);
|
||||
public ResetNetworkOperationBuilder resetEsimResultCallback(Consumer<Boolean> resultCallback) {
|
||||
mResetEsimResultCallback = resultCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a step of resetting E-SIM.
|
||||
* @param callerPackage package name of caller
|
||||
* @param resultCallback a Consumer<Boolean> dealing with result of resetting eSIM
|
||||
* @return this
|
||||
*/
|
||||
public ResetNetworkOperationBuilder resetEsim(String callerPackage,
|
||||
Consumer<Boolean> resultCallback) {
|
||||
public ResetNetworkOperationBuilder resetEsim(String callerPackage) {
|
||||
Runnable runnable = () -> {
|
||||
long startTime = SystemClock.elapsedRealtime();
|
||||
|
||||
if (!DRY_RUN) {
|
||||
Boolean wipped = RecoverySystem.wipeEuiccData(mContext, callerPackage);
|
||||
if (resultCallback != null) {
|
||||
resultCallback.accept(wipped);
|
||||
}
|
||||
boolean wipped;
|
||||
if (DRY_RUN) {
|
||||
wipped = true;
|
||||
} else {
|
||||
wipped = RecoverySystem.wipeEuiccData(mContext, callerPackage);
|
||||
}
|
||||
if (mResetEsimResultCallback != null) {
|
||||
mResetEsimResultCallback.accept(wipped);
|
||||
}
|
||||
|
||||
long endTime = SystemClock.elapsedRealtime();
|
||||
|
217
src/com/android/settings/system/reset/ResetNetworkConfirm.kt
Normal file
217
src/com/android/settings/system/reset/ResetNetworkConfirm.kt
Normal 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"
|
||||
}
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user