/* * 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 mResetSequence = new ArrayList(); /** * 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) cm -> { cm.factoryReset(); }); return this; } /** * Append a step of resetting VpnManager. * @return this */ public ResetNetworkOperationBuilder resetVpnManager() { attachSystemServiceWork(Context.VPN_MANAGEMENT_SERVICE, (Consumer) vpnManager -> { vpnManager.factoryReset(); }); return this; } /** * Append a step of resetting WifiManager. * @return this */ public ResetNetworkOperationBuilder resetWifiManager() { attachSystemServiceWork(Context.WIFI_SERVICE, (Consumer) 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.Channel channel = wifiP2pManager.initialize( mContext, callbackLooper, null /* listener */); if (channel != null) { wifiP2pManager.factoryReset(channel, null /* listener */); } }); return this; } /** * Append a step of resetting E-SIM. * @param callerPackage package name of caller * @return this */ public ResetNetworkOperationBuilder resetEsim(String callerPackage) { resetEsim(callerPackage, null); return this; } /** * Append a step of resetting E-SIM. * @param callerPackage package name of caller * @param resultCallback a Consumer dealing with result of resetting eSIM * @return this */ public ResetNetworkOperationBuilder resetEsim(String callerPackage, Consumer resultCallback) { Runnable runnable = () -> { long startTime = SystemClock.elapsedRealtime(); if (!DRY_RUN) { Boolean wipped = RecoverySystem.wipeEuiccData(mContext, callerPackage); if (resultCallback != null) { resultCallback.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 subscriberId = new AtomicReference(); attachSystemServiceWork(Context.TELEPHONY_SERVICE, (Consumer) tm -> { TelephonyManager subIdTm = tm.createForSubscriptionId(subscriptionId); subscriberId.set(subIdTm.getSubscriberId()); subIdTm.resetSettings(); }); attachSystemServiceWork(Context.NETWORK_POLICY_SERVICE, (Consumer) policyManager -> { policyManager.factoryReset(subscriberId.get()); }); return this; } /** * Append a step of resetting BluetoothAdapter. * @return this */ public ResetNetworkOperationBuilder resetBluetoothManager() { attachSystemServiceWork(Context.BLUETOOTH_SERVICE, (Consumer) 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) 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 void attachSystemServiceWork(String serviceName, Consumer 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()); } }