Files
app_Settings/src/com/android/settings/network/ResetNetworkOperationBuilder.java
Chaohui Wang b70c805717 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
2024-06-26 11:02:56 +08:00

345 lines
12 KiB
Java

/*
* 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.network;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkPolicyManager;
import android.net.Uri;
import android.net.VpnManager;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Looper;
import android.os.RecoverySystem;
import android.os.RemoteException;
import android.os.SystemClock;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.ResetNetworkRequest;
import com.android.settings.network.apn.PreferredApnRepository;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
/**
* A builder for creating a Runnable resetting network configurations.
*/
public class ResetNetworkOperationBuilder {
private static final String TAG = "ResetNetworkOpBuilder";
private static final boolean DRY_RUN = false;
// TelephonyContentProvider method to restart phone process
@VisibleForTesting
static final String METHOD_RESTART_PHONE_PROCESS = "restartPhoneProcess";
// TelephonyContentProvider method to restart RILD
@VisibleForTesting
static final String METHOD_RESTART_RILD = "restartRild";
private Context mContext;
private List<Runnable> mResetSequence = new ArrayList<Runnable>();
@Nullable
private Consumer<Boolean> mResetEsimResultCallback = null;
/**
* Constructor of builder.
*
* @param context Context
*/
public ResetNetworkOperationBuilder(Context context) {
mContext = context;
}
/**
* Append a step of resetting ConnectivityManager.
* @return this
*/
public ResetNetworkOperationBuilder resetConnectivityManager() {
attachSystemServiceWork(Context.CONNECTIVITY_SERVICE,
(Consumer<ConnectivityManager>) cm -> {
cm.factoryReset();
});
return this;
}
/**
* Append a step of resetting VpnManager.
* @return this
*/
public ResetNetworkOperationBuilder resetVpnManager() {
attachSystemServiceWork(Context.VPN_MANAGEMENT_SERVICE,
(Consumer<VpnManager>) vpnManager -> {
vpnManager.factoryReset();
});
return this;
}
/**
* Append a step of resetting WifiManager.
* @return this
*/
public ResetNetworkOperationBuilder resetWifiManager() {
attachSystemServiceWork(Context.WIFI_SERVICE,
(Consumer<WifiManager>) wifiManager -> {
wifiManager.factoryReset();
});
return this;
}
/**
* Append a step of resetting WifiP2pManager.
* @param callbackLooper looper to support callback from WifiP2pManager
* @return this
*/
public ResetNetworkOperationBuilder resetWifiP2pManager(Looper callbackLooper) {
attachSystemServiceWork(Context.WIFI_P2P_SERVICE,
(Consumer<WifiP2pManager>) wifiP2pManager -> {
WifiP2pManager.Channel channel = wifiP2pManager.initialize(
mContext, callbackLooper, null /* listener */);
if (channel != null) {
wifiP2pManager.factoryReset(channel, null /* listener */);
}
});
return this;
}
/**
* Append a result callback of resetting E-SIM.
* @param resultCallback a callback dealing with result of resetting eSIM
* @return this
*/
public ResetNetworkOperationBuilder resetEsimResultCallback(Consumer<Boolean> resultCallback) {
mResetEsimResultCallback = resultCallback;
return this;
}
/**
* Append a step of resetting E-SIM.
* @param callerPackage package name of caller
* @return this
*/
public ResetNetworkOperationBuilder resetEsim(String callerPackage) {
Runnable runnable = () -> {
long startTime = SystemClock.elapsedRealtime();
boolean wipped;
if (DRY_RUN) {
wipped = true;
} else {
wipped = RecoverySystem.wipeEuiccData(mContext, callerPackage);
}
if (mResetEsimResultCallback != null) {
mResetEsimResultCallback.accept(wipped);
}
long endTime = SystemClock.elapsedRealtime();
Log.i(TAG, "Reset eSIM, takes " + (endTime - startTime) + " ms");
};
mResetSequence.add(runnable);
return this;
}
/**
* Append a step of resetting TelephonyManager and .
* @param subscriptionId of a SIM card
* @return this
*/
public ResetNetworkOperationBuilder resetTelephonyAndNetworkPolicyManager(
int subscriptionId) {
final AtomicReference<String> subscriberId = new AtomicReference<String>();
attachSystemServiceWork(Context.TELEPHONY_SERVICE,
(Consumer<TelephonyManager>) tm -> {
TelephonyManager subIdTm = tm.createForSubscriptionId(subscriptionId);
subscriberId.set(subIdTm.getSubscriberId());
subIdTm.resetSettings();
});
attachSystemServiceWork(Context.NETWORK_POLICY_SERVICE,
(Consumer<NetworkPolicyManager>) policyManager -> {
policyManager.factoryReset(subscriberId.get());
});
return this;
}
/**
* Append a step of resetting BluetoothAdapter.
* @return this
*/
public ResetNetworkOperationBuilder resetBluetoothManager() {
attachSystemServiceWork(Context.BLUETOOTH_SERVICE,
(Consumer<BluetoothManager>) btManager -> {
BluetoothAdapter btAdapter = btManager.getAdapter();
if (btAdapter != null) {
btAdapter.clearBluetooth();
}
});
return this;
}
/**
* Append a step of resetting APN configurations.
* @param subscriptionId of a SIM card
* @return this
*/
public ResetNetworkOperationBuilder resetApn(int subscriptionId) {
Runnable runnable = () -> {
long startTime = SystemClock.elapsedRealtime();
Uri uri = PreferredApnRepository.getRestorePreferredApnUri();
if (SubscriptionManager.isUsableSubscriptionId(subscriptionId)) {
uri = Uri.withAppendedPath(uri, "subId/" + String.valueOf(subscriptionId));
}
if (!DRY_RUN) {
ContentResolver resolver = mContext.getContentResolver();
resolver.delete(uri, null, null);
}
long endTime = SystemClock.elapsedRealtime();
Log.i(TAG, "Reset " + uri + ", takes " + (endTime - startTime) + " ms");
};
mResetSequence.add(runnable);
return this;
}
/**
* Append a step of resetting IMS stack.
*
* @return this
*/
public ResetNetworkOperationBuilder resetIms(int subId) {
attachSystemServiceWork(Context.TELEPHONY_SERVICE,
(Consumer<TelephonyManager>) tm -> {
if (subId == ResetNetworkRequest.INVALID_SUBSCRIPTION_ID) {
// Do nothing
return;
}
if (subId == ResetNetworkRequest.ALL_SUBSCRIPTION_ID) {
// Reset IMS for all slots
for (int slotIndex = 0; slotIndex < tm.getActiveModemCount(); slotIndex++) {
tm.resetIms(slotIndex);
Log.i(TAG, "IMS was reset for slot " + slotIndex);
}
} else {
// Reset IMS for the slot specified by the sucriptionId.
final int slotIndex = SubscriptionManager.getSlotIndex(subId);
tm.resetIms(slotIndex);
Log.i(TAG, "IMS was reset for slot " + slotIndex);
}
});
return this;
}
/**
* Append a step to restart phone process by the help of TelephonyContentProvider.
* It's a no-op if TelephonyContentProvider doesn't exist.
* @return this
*/
public ResetNetworkOperationBuilder restartPhoneProcess() {
Runnable runnable = () -> {
// Unstable content provider can avoid us getting killed together with phone process
try (ContentProviderClient client = getUnstableTelephonyContentProviderClient()) {
if (client != null) {
client.call(METHOD_RESTART_PHONE_PROCESS, /* arg= */ null, /* extra= */ null);
Log.i(TAG, "Phone process was restarted.");
}
} catch (RemoteException re) {
// It's normal to throw RE since phone process immediately dies
Log.i(TAG, "Phone process has been restarted: " + re);
}
};
mResetSequence.add(runnable);
return this;
}
/**
* Append a step to restart RILD by the help of TelephonyContentProvider.
* It's a no-op if TelephonyContentProvider doesn't exist.
* @return this
*/
public ResetNetworkOperationBuilder restartRild() {
Runnable runnable = () -> {
try (ContentProviderClient client = getUnstableTelephonyContentProviderClient()) {
if (client != null) {
client.call(METHOD_RESTART_RILD, /* arg= */ null, /* extra= */ null);
Log.i(TAG, "RILD was restarted.");
}
} catch (RemoteException re) {
Log.w(TAG, "Fail to restart RILD: " + re);
}
};
mResetSequence.add(runnable);
return this;
}
/**
* Construct a Runnable containing all operations appended.
* @return Runnable
*/
public Runnable build() {
return () -> mResetSequence.forEach(runnable -> runnable.run());
}
protected <T> void attachSystemServiceWork(String serviceName, Consumer<T> serviceAccess) {
T service = (T) mContext.getSystemService(serviceName);
if (service == null) {
return;
}
Runnable runnable = () -> {
long startTime = SystemClock.elapsedRealtime();
if (!DRY_RUN) {
serviceAccess.accept(service);
}
long endTime = SystemClock.elapsedRealtime();
Log.i(TAG, "Reset " + serviceName + ", takes " + (endTime - startTime) + " ms");
};
mResetSequence.add(runnable);
}
/**
* @return the authority of the telephony content provider that support methods
* resetPhoneProcess and resetRild.
*/
private String getResetTelephonyContentProviderAuthority() {
return mContext.getResources().getString(
R.string.reset_telephony_stack_content_provider_authority);
}
/**
* @return the unstable content provider to avoid us getting killed with phone process
*/
@Nullable
@VisibleForTesting
public ContentProviderClient getUnstableTelephonyContentProviderClient() {
return mContext.getContentResolver().acquireUnstableContentProviderClient(
getResetTelephonyContentProviderAuthority());
}
}