• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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