1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.system.reset 18 19 import android.app.ProgressDialog 20 import android.app.settings.SettingsEnums 21 import android.content.DialogInterface 22 import android.os.Bundle 23 import android.os.Looper 24 import android.telecom.TelecomManager 25 import android.telephony.SubscriptionManager 26 import android.util.Log 27 import android.view.LayoutInflater 28 import android.view.View 29 import android.view.ViewGroup 30 import android.widget.TextView 31 import android.widget.Toast 32 import androidx.annotation.VisibleForTesting 33 import androidx.appcompat.app.AlertDialog 34 import androidx.lifecycle.lifecycleScope 35 import com.android.settings.R 36 import com.android.settings.ResetNetworkRequest 37 import com.android.settings.Utils 38 import com.android.settings.core.InstrumentedFragment 39 import com.android.settings.network.ResetNetworkRestrictionViewBuilder 40 import com.android.settings.network.telephony.SubscriptionRepository 41 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle 42 import kotlinx.coroutines.Dispatchers 43 import kotlinx.coroutines.flow.Flow 44 import kotlinx.coroutines.flow.conflate 45 import kotlinx.coroutines.flow.emptyFlow 46 import kotlinx.coroutines.flow.flowOn 47 import kotlinx.coroutines.flow.mapNotNull 48 import kotlinx.coroutines.launch 49 import kotlinx.coroutines.withContext 50 51 /** 52 * Confirm and execute a reset of the network settings to a clean "just out of the box" state. 53 * Multiple confirmations are required: first, a general "are you sure you want to do this?" prompt, 54 * followed by a keyguard pattern trace if the user has defined one, followed by a final 55 * strongly-worded "THIS WILL RESET EVERYTHING" prompt. If at any time the phone is allowed to go to 56 * sleep, is locked, et cetera, then the confirmation sequence is abandoned. 57 * 58 * This is the confirmation screen. 59 */ 60 class ResetNetworkConfirm : InstrumentedFragment() { 61 @VisibleForTesting 62 lateinit var resetNetworkRequest: ResetNetworkRequest 63 private var progressDialog: ProgressDialog? = null 64 private var alertDialog: AlertDialog? = null 65 private var resetStarted = false 66 67 override fun onCreate(savedInstanceState: Bundle?) { 68 super.onCreate(savedInstanceState) 69 70 Log.d(TAG, "onCreate: $arguments") 71 resetNetworkRequest = ResetNetworkRequest(arguments) 72 } 73 74 override fun onCreateView( 75 inflater: LayoutInflater, 76 container: ViewGroup?, 77 savedInstanceState: Bundle? 78 ): View { 79 val view = ResetNetworkRestrictionViewBuilder(requireActivity()).build() 80 if (view != null) { 81 Log.w(TAG, "Access deny.") 82 return view 83 } 84 return inflater.inflate(R.layout.reset_network_confirm, null).apply { 85 establishFinalConfirmationState() 86 setSubtitle() 87 } 88 } 89 90 /** Configure the UI for the final confirmation interaction */ 91 private fun View.establishFinalConfirmationState() { 92 requireViewById<View>(R.id.execute_reset_network).setOnClickListener { 93 val tm = context.getSystemService(TelecomManager::class.java) 94 if (tm != null && tm.isInCall) { 95 showResetInternetDialog(); 96 } else { 97 onResetClicked() 98 } 99 } 100 } 101 102 private fun View.setSubtitle() { 103 if (resetNetworkRequest.resetEsimPackageName != null) { 104 requireViewById<TextView>(R.id.reset_network_confirm) 105 .setText(R.string.reset_network_final_desc_esim) 106 } 107 } 108 109 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 110 super.onViewCreated(view, savedInstanceState) 111 invalidSubIdFlow().collectLatestWithLifecycle(viewLifecycleOwner) { invalidSubId -> 112 // Reset process could triage this callback, so if reset has started, ignore the event. 113 if (!resetStarted) { 114 Log.w(TAG, "subId $invalidSubId no longer active.") 115 requireActivity().finish() 116 } 117 } 118 } 119 120 /** 121 * Monitor the sub ids in the request, if any sub id becomes inactive, the request is abandoned. 122 */ 123 private fun invalidSubIdFlow(): Flow<Int> { 124 val subIdsInRequest = 125 listOf( 126 resetNetworkRequest.resetTelephonyAndNetworkPolicyManager, 127 resetNetworkRequest.resetApnSubId, 128 resetNetworkRequest.resetImsSubId, 129 ) 130 .distinct() 131 .filter(SubscriptionManager::isUsableSubscriptionId) 132 133 if (subIdsInRequest.isEmpty()) return emptyFlow() 134 135 return SubscriptionRepository(requireContext()) 136 .activeSubscriptionIdListFlow() 137 .mapNotNull { activeSubIds -> subIdsInRequest.firstOrNull { it !in activeSubIds } } 138 .conflate() 139 .flowOn(Dispatchers.Default) 140 } 141 142 /** 143 * The user has gone through the multiple confirmation, so now we go ahead and reset the network 144 * settings to its factory-default state. 145 */ 146 @VisibleForTesting 147 fun onResetClicked() { 148 if (!Utils.isMonkeyRunning() && !resetStarted) { 149 resetStarted = true 150 viewLifecycleOwner.lifecycleScope.launch { 151 showProgressDialog() 152 resetNetwork() } 153 } 154 } 155 156 private fun showProgressDialog() { 157 progressDialog = 158 ProgressDialog(requireContext()).apply { 159 isIndeterminate = true 160 setCancelable(false) 161 setMessage(requireContext().getString(R.string.main_clear_progress_text)) 162 show() 163 } 164 } 165 166 private fun dismissProgressDialog() { 167 progressDialog?.let { progressDialog -> 168 if (progressDialog.isShowing) { 169 progressDialog.dismiss() 170 } 171 } 172 } 173 174 private fun showResetInternetDialog() { 175 val builder = AlertDialog.Builder(requireContext()) 176 val resetInternetClickListener = 177 DialogInterface.OnClickListener { dialog, which -> 178 onResetClicked() 179 } 180 181 builder.setTitle(R.string.reset_your_internet_title) 182 .setMessage(R.string.reset_internet_text) 183 .setPositiveButton(R.string.tts_reset, resetInternetClickListener) 184 .setNegativeButton(android.R.string.cancel, null) 185 .create() 186 .show() 187 } 188 189 /** 190 * Do all reset task. 191 * 192 * If error happens during erasing eSIM profiles or timeout, an error msg is shown. 193 */ 194 private suspend fun resetNetwork() { 195 var resetEsimSuccess = true 196 197 withContext(Dispatchers.Default) { 198 val builder = 199 resetNetworkRequest.toResetNetworkOperationBuilder( 200 requireContext(), Looper.getMainLooper() 201 ) 202 resetNetworkRequest.resetEsimPackageName?.let { 203 builder.resetEsimResultCallback { resetEsimSuccess = it } 204 } 205 builder.build().run() 206 } 207 208 Log.d(TAG, "network factoryReset complete. succeeded: $resetEsimSuccess") 209 onResetFinished(resetEsimSuccess) 210 } 211 212 private fun onResetFinished(resetEsimSuccess: Boolean) { 213 dismissProgressDialog() 214 val activity = requireActivity() 215 216 if (!resetEsimSuccess) { 217 alertDialog = 218 AlertDialog.Builder(activity) 219 .setTitle(R.string.reset_esim_error_title) 220 .setMessage(R.string.reset_esim_error_msg) 221 .setPositiveButton(android.R.string.ok, /* listener= */ null) 222 .show() 223 } else { 224 Toast.makeText(activity, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT) 225 .show() 226 activity.finish() 227 } 228 } 229 230 override fun onDestroy() { 231 progressDialog?.dismiss() 232 alertDialog?.dismiss() 233 super.onDestroy() 234 } 235 236 override fun getMetricsCategory() = SettingsEnums.RESET_NETWORK_CONFIRM 237 238 private companion object { 239 const val TAG = "ResetNetworkConfirm" 240 } 241 } 242