Merge changes I51395a55,I63a86569 into main
* changes: Fix unable to erase eSIM SubscriptionRepository.activeSubscriptionIdListFlow
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();
|
||||
|
@@ -25,14 +25,17 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class CallStateRepository(private val context: Context) {
|
||||
private val subscriptionManager = context.requireSubscriptionManager()
|
||||
class CallStateRepository(
|
||||
private val context: Context,
|
||||
private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
|
||||
) {
|
||||
|
||||
/** Flow for call state of given [subId]. */
|
||||
fun callStateFlow(subId: Int): Flow<Int> = context.telephonyCallbackFlow(subId) {
|
||||
@@ -48,9 +51,8 @@ class CallStateRepository(private val context: Context) {
|
||||
*
|
||||
* @return true if any active subscription's call state is not idle.
|
||||
*/
|
||||
fun isInCallFlow(): Flow<Boolean> = context.subscriptionsChangedFlow()
|
||||
.flatMapLatest {
|
||||
val subIds = subscriptionManager.activeSubscriptionIdList
|
||||
fun isInCallFlow(): Flow<Boolean> = subscriptionRepository.activeSubscriptionIdListFlow()
|
||||
.flatMapLatest { subIds ->
|
||||
if (subIds.isEmpty()) {
|
||||
flowOf(false)
|
||||
} else {
|
||||
@@ -59,9 +61,10 @@ class CallStateRepository(private val context: Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.conflate()
|
||||
.flowOn(Dispatchers.Default)
|
||||
.onEach { Log.d(TAG, "isInCallFlow: $it") }
|
||||
.flowOn(Dispatchers.Default)
|
||||
|
||||
private companion object {
|
||||
private const val TAG = "CallStateRepository"
|
||||
|
@@ -29,6 +29,7 @@ import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNot
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
@@ -68,6 +69,30 @@ class SubscriptionRepository(private val context: Context) {
|
||||
}
|
||||
|
||||
fun canDisablePhysicalSubscription() = subscriptionManager.canDisablePhysicalSubscription()
|
||||
|
||||
/** Flow for subscriptions changes. */
|
||||
fun subscriptionsChangedFlow() = callbackFlow {
|
||||
val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
|
||||
override fun onSubscriptionsChanged() {
|
||||
trySend(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionManager.addOnSubscriptionsChangedListener(
|
||||
Dispatchers.Default.asExecutor(),
|
||||
listener,
|
||||
)
|
||||
|
||||
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
|
||||
}.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
|
||||
|
||||
/** Flow of active subscription ids. */
|
||||
fun activeSubscriptionIdListFlow(): Flow<List<Int>> = context.subscriptionsChangedFlow()
|
||||
.map { subscriptionManager.activeSubscriptionIdList.sorted() }
|
||||
.distinctUntilChanged()
|
||||
.conflate()
|
||||
.onEach { Log.d(TAG, "activeSubscriptionIdList: $it") }
|
||||
.flowOn(Dispatchers.Default)
|
||||
}
|
||||
|
||||
val Context.subscriptionManager: SubscriptionManager?
|
||||
@@ -79,22 +104,8 @@ fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsC
|
||||
SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo)
|
||||
}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
|
||||
|
||||
fun Context.subscriptionsChangedFlow() = callbackFlow {
|
||||
val subscriptionManager = requireSubscriptionManager()
|
||||
|
||||
val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
|
||||
override fun onSubscriptionsChanged() {
|
||||
trySend(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionManager.addOnSubscriptionsChangedListener(
|
||||
Dispatchers.Default.asExecutor(),
|
||||
listener,
|
||||
)
|
||||
|
||||
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
|
||||
}.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
|
||||
fun Context.subscriptionsChangedFlow(): Flow<Unit> =
|
||||
SubscriptionRepository(this).subscriptionsChangedFlow()
|
||||
|
||||
/**
|
||||
* Return a list of subscriptions that are available and visible to the user.
|
||||
|
@@ -25,10 +25,9 @@ import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.settings.R
|
||||
import com.android.settings.network.telephony.MobileDataRepository
|
||||
import com.android.settings.network.telephony.SubscriptionRepository
|
||||
import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
|
||||
import com.android.settings.network.telephony.requireSubscriptionManager
|
||||
import com.android.settings.network.telephony.safeGetConfig
|
||||
import com.android.settings.network.telephony.subscriptionsChangedFlow
|
||||
import com.android.settings.network.telephony.telephonyManager
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -48,7 +47,7 @@ class CrossSimCallingViewModel(
|
||||
private val application: Application,
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
private val subscriptionManager = application.requireSubscriptionManager()
|
||||
private val subscriptionRepository = SubscriptionRepository(application)
|
||||
private val carrierConfigManager =
|
||||
application.getSystemService(CarrierConfigManager::class.java)!!
|
||||
private val scope = viewModelScope + Dispatchers.Default
|
||||
@@ -59,9 +58,8 @@ class CrossSimCallingViewModel(
|
||||
init {
|
||||
val resources = application.resources
|
||||
if (resources.getBoolean(R.bool.config_auto_data_switch_enables_cross_sim_calling)) {
|
||||
application.subscriptionsChangedFlow()
|
||||
.flatMapLatest {
|
||||
val activeSubIds = subscriptionManager.activeSubscriptionIdList.toList()
|
||||
subscriptionRepository.activeSubscriptionIdListFlow()
|
||||
.flatMapLatest { activeSubIds ->
|
||||
merge(
|
||||
activeSubIds.anyMobileDataEnableChangedFlow(),
|
||||
updateChannel.receiveAsFlow(),
|
||||
|
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"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user