diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2ba87d7099a..02dd9cdf393 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5036,6 +5036,12 @@ android:authorities="${applicationId}.androidx-startup" tools:node="remove" /> + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 35c175997c0..776e3f41093 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3323,6 +3323,8 @@ Cancel + + Duplicate apn entry. The Name field can\u2019t be empty. diff --git a/src/com/android/settings/accessibility/HearingDevicePairingFragment.java b/src/com/android/settings/accessibility/HearingDevicePairingFragment.java index fb79ece55bf..78f5b4c1b6e 100644 --- a/src/com/android/settings/accessibility/HearingDevicePairingFragment.java +++ b/src/com/android/settings/accessibility/HearingDevicePairingFragment.java @@ -149,6 +149,7 @@ public class HearingDevicePairingFragment extends RestrictedDashboardFragment im for (BluetoothGatt gatt: mConnectingGattList) { gatt.disconnect(); } + mConnectingGattList.clear(); mLocalManager.setForegroundActivity(null); mLocalManager.getEventManager().unregisterCallback(this); } diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java index f14c32c5381..0da1034994c 100644 --- a/src/com/android/settings/network/NetworkProviderSettings.java +++ b/src/com/android/settings/network/NetworkProviderSettings.java @@ -89,6 +89,7 @@ import com.android.settingslib.widget.FooterPreference; import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.settingslib.wifi.WifiSavedConfigUtils; +import com.android.wifi.flags.Flags; import com.android.wifitrackerlib.WifiEntry; import com.android.wifitrackerlib.WifiEntry.ConnectCallback; import com.android.wifitrackerlib.WifiPickerTracker; @@ -1257,8 +1258,19 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment // If it's an unsaved secure WifiEntry, it will callback // ConnectCallback#onConnectResult with ConnectCallback#CONNECT_STATUS_FAILURE_NO_CONFIG - wifiEntry.connect(new WifiEntryConnectCallback(wifiEntry, editIfNoConfig, - fullScreenEdit)); + WifiEntryConnectCallback callback = + new WifiEntryConnectCallback(wifiEntry, editIfNoConfig, fullScreenEdit); + + if (Flags.wepUsage() && wifiEntry.getSecurityTypes().contains(WifiEntry.SECURITY_WEP)) { + WepNetworkDialogActivity.checkWepAllowed( + getContext(), getViewLifecycleOwner(), wifiEntry.getSsid(), () -> { + wifiEntry.connect(callback); + return null; + }); + return; + } + + wifiEntry.connect(callback); } private class WifiConnectActionListener implements WifiManager.ActionListener { diff --git a/src/com/android/settings/network/WepNetworkDialogActivity.kt b/src/com/android/settings/network/WepNetworkDialogActivity.kt new file mode 100644 index 00000000000..2fa87849ef1 --- /dev/null +++ b/src/com/android/settings/network/WepNetworkDialogActivity.kt @@ -0,0 +1,111 @@ +/* + * 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.network + +import android.app.settings.SettingsEnums +import android.content.Context +import android.content.Intent +import android.net.wifi.WifiManager +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextAlign +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.android.settings.R +import com.android.settings.core.SubSettingLauncher +import com.android.settings.wifi.ConfigureWifiSettings +import com.android.settingslib.spa.SpaBaseDialogActivity +import com.android.settingslib.spa.widget.dialog.AlertDialogButton +import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon +import kotlin.coroutines.resume +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext + +class WepNetworkDialogActivity : SpaBaseDialogActivity() { + @Composable + override fun Content() { + val context = LocalContext.current + val wifiManager = context.getSystemService(WifiManager::class.java) + SettingsAlertDialogWithIcon( + onDismissRequest = { finish() }, + confirmButton = AlertDialogButton( + getString(R.string.wifi_settings_ssid_block_button_close) + ) { finish() }, + dismissButton = AlertDialogButton( + getString(R.string.wifi_settings_wep_networks_button_allow) + ) { + SubSettingLauncher(context) + .setTitleText(context.getText(R.string.network_and_internet_preferences_title)) + .setSourceMetricsCategory(SettingsEnums.CONFIGURE_WIFI) + .setDestination(ConfigureWifiSettings::class.java.getName()) + .launch() + finish() + }, + title = String.format( + getString(R.string.wifi_settings_wep_networks_blocked_title), + intent.getStringExtra(SSID) ?: SSID + ), + text = { + Text( + if (wifiManager?.isWepSupported == false) + getString(R.string.wifi_settings_wep_networks_summary_toggle_off) + else getString(R.string.wifi_settings_wep_networks_summary_blocked_by_carrier), + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + }) + } + + companion object { + @JvmStatic + fun checkWepAllowed( + context: Context, + lifecycleOwner: LifecycleOwner, + ssid: String, + onAllowed: () -> Unit, + ) { + lifecycleOwner.lifecycleScope.launch { + val wifiManager = context.getSystemService(WifiManager::class.java) ?: return@launch + if (wifiManager.queryWepAllowed()) { + onAllowed() + } else { + val intent = Intent(context, WepNetworkDialogActivity::class.java).apply { + putExtra(SSID, ssid) + } + context.startActivity(intent) + } + } + } + + private suspend fun WifiManager.queryWepAllowed(): Boolean = + withContext(Dispatchers.Default) { + suspendCancellableCoroutine { continuation -> + queryWepAllowed(Dispatchers.Default.asExecutor()) { + continuation.resume(it) + } + } + } + + const val SSID = "ssid" + } +} \ No newline at end of file diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt index 2600618caae..924e311ffdc 100644 --- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt +++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt @@ -19,16 +19,19 @@ package com.android.settings.network.apn import android.net.Uri import android.os.Bundle import androidx.compose.foundation.layout.Column -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Done -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource @@ -39,14 +42,14 @@ import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeDisplayNam import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeSelectedOptionsState import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.LocalNavController +import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField import com.android.settingslib.spa.widget.editor.SettingsTextFieldPassword -import com.android.settingslib.spa.widget.preference.Preference -import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction import com.android.settingslib.spa.widget.scaffold.RegularScaffold import java.util.Base64 @@ -98,25 +101,59 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur getNetworkTypeSelectedOptionsState(apnData.networkType) } val navController = LocalNavController.current + var valid: String? RegularScaffold( title = if (apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit), actions = { if (!apnData.customizedConfig.readOnlyApn) { - IconButton(onClick = { - if (!apnData.validEnabled) apnData = apnData.copy(validEnabled = true) - val valid = validateAndSaveApnData( + Button(onClick = { + apnData = apnData.copy( + networkType = ApnNetworkTypes.getNetworkType( + networkTypeSelectedOptionsState + ) + ) + valid = validateAndSaveApnData( apnDataInit, apnData, context, - uriInit, - networkTypeSelectedOptionsState + uriInit ) - if (valid) navController.navigateBack() - }) { Icon(imageVector = Icons.Outlined.Done, contentDescription = null) } + if (valid == null) navController.navigateBack() + else if (!apnData.validEnabled) apnData = apnData.copy(validEnabled = true) + }) { Text(text = stringResource(id = R.string.save)) } + } + if (!apnData.newApn && !apnData.customizedConfig.readOnlyApn + && apnData.customizedConfig.isAddApnAllowed + ) { + MoreOptionsAction { + DropdownMenuItem( + text = { Text(stringResource(R.string.menu_delete)) }, + onClick = { + deleteApn(uriInit, context) + navController.navigateBack() + }) + } } }, ) { Column { + if (apnData.validEnabled) { + apnData = apnData.copy( + networkType = ApnNetworkTypes.getNetworkType( + networkTypeSelectedOptionsState + ) + ) + valid = validateApnData(uriInit, apnData, context) + valid?.let { + Text( + text = it, + modifier = Modifier + .fillMaxWidth() + .padding(SettingsDimension.menuFieldPadding), + color = MaterialTheme.colorScheme.primary + ) + } + } SettingsOutlinedTextField( value = apnData.name, label = stringResource(R.string.apn_name), @@ -214,19 +251,6 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur emptyVal = stringResource(R.string.network_type_unspecified), enabled = apnData.networkTypeEnabled ) {} - if (!apnData.newApn && !apnData.customizedConfig.readOnlyApn - && apnData.customizedConfig.isAddApnAllowed - ) { - Preference( - object : PreferenceModel { - override val title = stringResource(R.string.menu_delete) - override val onClick = { - deleteApn(uriInit, context) - navController.navigateBack() - } - } - ) - } } } } \ No newline at end of file diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt index e0121b4926a..2f16e693adb 100644 --- a/src/com/android/settings/network/apn/ApnRepository.kt +++ b/src/com/android/settings/network/apn/ApnRepository.kt @@ -20,6 +20,7 @@ import android.content.ContentValues import android.content.Context import android.net.Uri import android.provider.Telephony +import android.telephony.TelephonyManager import android.util.Log import com.android.settings.R import com.android.settingslib.utils.ThreadUtils @@ -150,7 +151,6 @@ fun getApnDataFromUri(uri: Uri, context: Context): ApnData { private fun convertProtocol2Options(raw: String, context: Context): String { val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList() val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList() - var uRaw = raw.uppercase(Locale.getDefault()) uRaw = if (uRaw == "IPV4") "IP" else uRaw val protocolIndex = apnProtocolValues.indexOf(uRaw) @@ -167,7 +167,6 @@ private fun convertProtocol2Options(raw: String, context: Context): String { fun convertOptions2Protocol(protocolIndex: Int, context: Context): String { val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList() - return if (protocolIndex == -1) { "" } else { @@ -179,7 +178,12 @@ fun convertOptions2Protocol(protocolIndex: Int, context: Context): String { } } -fun updateApnDataToDatabase(newApn: Boolean, values: ContentValues, context: Context, uriInit: Uri) { +fun updateApnDataToDatabase( + newApn: Boolean, + values: ContentValues, + context: Context, + uriInit: Uri +) { ThreadUtils.postOnBackgroundThread { if (newApn) { // Add a new apn to the database @@ -194,4 +198,24 @@ fun updateApnDataToDatabase(newApn: Boolean, values: ContentValues, context: Con ) } } +} + +fun isItemExist(uri: Uri, apnData: ApnData, context: Context): String? { + val contentValueMap = apnData.getContentValueMap(context) + contentValueMap.remove(Telephony.Carriers.CARRIER_ENABLED) + val list = contentValueMap.entries.toList() + val selection = list.joinToString(" AND ") { "${it.key} = ?" } + val selectionArgs: Array = list.map { it.value.toString() }.toTypedArray() + context.contentResolver.query( + uri, + sProjection, + selection /* selection */, + selectionArgs /* selectionArgs */, + null /* sortOrder */ + )?.use { cursor -> + if (cursor.count > 0) { + return context.resources.getString(R.string.error_duplicate_apn_entry) + } + } + return null } \ No newline at end of file diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt index e4cb603fcb7..6f39305f3cf 100644 --- a/src/com/android/settings/network/apn/ApnStatus.kt +++ b/src/com/android/settings/network/apn/ApnStatus.kt @@ -72,41 +72,38 @@ data class ApnData( val validEnabled: Boolean = false, val customizedConfig: CustomizedConfig = CustomizedConfig() ) { + fun getContentValueMap(context: Context): MutableMap { + val simCarrierId = + context.getSystemService(TelephonyManager::class.java)!! + .createForSubscriptionId(subId) + .getSimCarrierId() + return mutableMapOf( + Telephony.Carriers.NAME to name, Telephony.Carriers.APN to apn, + Telephony.Carriers.PROXY to proxy, Telephony.Carriers.PORT to port, + Telephony.Carriers.MMSPROXY to mmsProxy, Telephony.Carriers.MMSPORT to mmsPort, + Telephony.Carriers.USER to userName, Telephony.Carriers.SERVER to server, + Telephony.Carriers.PASSWORD to passWord, Telephony.Carriers.MMSC to mmsc, + Telephony.Carriers.AUTH_TYPE to authType, + Telephony.Carriers.PROTOCOL to convertOptions2Protocol(apnProtocol, context), + Telephony.Carriers.ROAMING_PROTOCOL to convertOptions2Protocol(apnRoaming, context), + Telephony.Carriers.TYPE to apnType, + Telephony.Carriers.NETWORK_TYPE_BITMASK to networkType, + Telephony.Carriers.CARRIER_ENABLED to apnEnable, + Telephony.Carriers.EDITED_STATUS to Telephony.Carriers.USER_EDITED, + Telephony.Carriers.CARRIER_ID to simCarrierId + ) + } + fun getContentValues(context: Context): ContentValues { val values = ContentValues() - values.put(Telephony.Carriers.NAME, name) - values.put(Telephony.Carriers.APN, apn) - values.put(Telephony.Carriers.PROXY, proxy) - values.put(Telephony.Carriers.PORT, port) - values.put(Telephony.Carriers.MMSPROXY, mmsProxy) - values.put(Telephony.Carriers.MMSPORT, mmsPort) - values.put(Telephony.Carriers.USER, userName) - values.put(Telephony.Carriers.SERVER, server) - values.put(Telephony.Carriers.PASSWORD, passWord) - values.put(Telephony.Carriers.MMSC, mmsc) - values.put(Telephony.Carriers.AUTH_TYPE, authType) - values.put(Telephony.Carriers.PROTOCOL, convertOptions2Protocol(apnProtocol, context)) - values.put( - Telephony.Carriers.ROAMING_PROTOCOL, - convertOptions2Protocol(apnRoaming, context) - ) - values.put(Telephony.Carriers.TYPE, apnType) - values.put(Telephony.Carriers.NETWORK_TYPE_BITMASK, networkType) - values.put(Telephony.Carriers.CARRIER_ENABLED, apnEnable) - values.put(Telephony.Carriers.EDITED_STATUS, Telephony.Carriers.USER_EDITED) - if (newApn) { - val simCarrierId = - context.getSystemService(TelephonyManager::class.java)!! - .createForSubscriptionId(subId) - .getSimCarrierId() - values.put(Telephony.Carriers.CARRIER_ID, simCarrierId) - } + val contentValueMap = getContentValueMap(context) + if (!newApn) contentValueMap.remove(Telephony.Carriers.CARRIER_ID) + contentValueMap.forEach { (key, value) -> values.putObject(key, value) } return values } } data class CustomizedConfig( - val newApn: Boolean = false, val readOnlyApn: Boolean = false, val isAddApnAllowed: Boolean = true, val readOnlyApnTypes: List = emptyList(), @@ -227,20 +224,14 @@ fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int */ fun validateAndSaveApnData( apnDataInit: ApnData, - apnData: ApnData, + newApnData: ApnData, context: Context, - uriInit: Uri, - networkTypeSelectedOptionsState: SnapshotStateList -): Boolean { - // Nothing to do if it's a read only APN - if (apnData.customizedConfig.readOnlyApn) { - return true - } - val errorMsg = validateApnData(apnData, context) + uriInit: Uri +): String? { + val errorMsg = validateApnData(uriInit, newApnData, context) if (errorMsg != null) { - return false + return errorMsg } - val newApnData = apnData.copy(networkType = getNetworkType(networkTypeSelectedOptionsState)) if (newApnData.newApn || (newApnData != apnDataInit)) { Log.d(TAG, "[validateAndSaveApnData] newApnData.networkType: ${newApnData.networkType}") updateApnDataToDatabase( @@ -250,7 +241,7 @@ fun validateAndSaveApnData( uriInit ) } - return true + return null } /** @@ -258,7 +249,7 @@ fun validateAndSaveApnData( * * @return An error message if the apn data is invalid, otherwise return null. */ -fun validateApnData(apnData: ApnData, context: Context): String? { +fun validateApnData(uri: Uri, apnData: ApnData, context: Context): String? { var errorMsg: String? val name = apnData.name val apn = apnData.apn @@ -267,11 +258,14 @@ fun validateApnData(apnData: ApnData, context: Context): String? { } else if (apn == "") { context.resources.getString(R.string.error_apn_empty) } else { - validateMMSC(apnData.validEnabled, apnData.mmsc, context) + validateMMSC(true, apnData.mmsc, context) + } + if (errorMsg == null) { + errorMsg = isItemExist(uri, apnData, context) } if (errorMsg == null) { errorMsg = validateAPNType( - apnData.validEnabled, + true, apnData.apnType, apnData.customizedConfig.readOnlyApnTypes, context diff --git a/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt b/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt index e4fb1ea47a3..38a84998e07 100644 --- a/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt +++ b/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt @@ -104,7 +104,7 @@ class AppArchiveButton( userHandle ) try { - packageInstaller.requestArchive(app.packageName, pendingIntent.intentSender, 0) + packageInstaller.requestArchive(app.packageName, pendingIntent.intentSender) } catch (e: Exception) { Log.e(LOG_TAG, "Request archive failed", e) Toast.makeText( diff --git a/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt b/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt index a7f971418cd..aafe49382db 100644 --- a/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt @@ -56,9 +56,12 @@ class VoiceActivationAppsListModel(context: Context) : AppOpPermissionListModel( override val appOp = AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO override val permission = Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO override val setModeByUid = true - + private var receiveDetectionTrainingDataOpController:AppOpsController? = null override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { super.setAllowed(record, newAllowed) + if (!newAllowed && receiveDetectionTrainingDataOpController != null) { + receiveDetectionTrainingDataOpController!!.setAllowed(false) + } logPermissionChange(newAllowed) } @@ -79,20 +82,21 @@ class VoiceActivationAppsListModel(context: Context) : AppOpPermissionListModel( isReceiveSandBoxTriggerAudioOpAllowed: () -> Boolean? ): ReceiveDetectionTrainingDataOpSwitchModel { val context = LocalContext.current - val ReceiveDetectionTrainingDataOpController = remember { + receiveDetectionTrainingDataOpController = remember { AppOpsController( context = context, app = record.app, op = AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, ) } - val isReceiveDetectionTrainingDataOpAllowed = isReceiveDetectionTrainingDataOpAllowed(record, ReceiveDetectionTrainingDataOpController) + val isReceiveDetectionTrainingDataOpAllowed = isReceiveDetectionTrainingDataOpAllowed(record, receiveDetectionTrainingDataOpController!!) + return remember(record) { ReceiveDetectionTrainingDataOpSwitchModel( context, record, isReceiveSandBoxTriggerAudioOpAllowed, - ReceiveDetectionTrainingDataOpController, + receiveDetectionTrainingDataOpController!!, isReceiveDetectionTrainingDataOpAllowed, ) }.also { model -> LaunchedEffect(model, Dispatchers.Default) { model.initState() } } diff --git a/src/com/android/settings/system/FactoryResetPreferenceController.java b/src/com/android/settings/system/FactoryResetPreferenceController.java index e62e548fec0..6d811797188 100644 --- a/src/com/android/settings/system/FactoryResetPreferenceController.java +++ b/src/com/android/settings/system/FactoryResetPreferenceController.java @@ -28,6 +28,7 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.preference.Preference; +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.Settings; import com.android.settings.core.BasePreferenceController; import com.android.settings.factory_reset.Flags; @@ -36,11 +37,14 @@ public class FactoryResetPreferenceController extends BasePreferenceController { private static final String TAG = "FactoryResetPreference"; - private static final String ACTION_PREPARE_FACTORY_RESET = + @VisibleForTesting + static final String ACTION_PREPARE_FACTORY_RESET = "com.android.settings.ACTION_PREPARE_FACTORY_RESET"; private final UserManager mUm; - private ActivityResultLauncher mFactoryResetPreparationLauncher; + + @VisibleForTesting + ActivityResultLauncher mFactoryResetPreparationLauncher; public FactoryResetPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); diff --git a/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java index 321fcf58b9f..01d0df9981b 100644 --- a/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java @@ -17,75 +17,125 @@ package com.android.settings.system; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.os.UserHandle; +import android.os.UserManager; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; -import com.android.settings.testutils.shadow.ShadowUserManager; -import com.android.settings.testutils.shadow.ShadowUtils; +import androidx.activity.result.ActivityResultLauncher; +import androidx.preference.Preference; + +import com.google.common.collect.ImmutableList; import org.junit.After; import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowUserManager.class) public class FactoryResetPreferenceControllerTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final String FACTORY_RESET_KEY = "factory_reset"; + private static final String FACTORY_RESET_APP_PACKAGE = "com.frw_app"; - private ShadowUserManager mShadowUserManager; + @Mock private ActivityResultLauncher mFactoryResetLauncher; + @Mock private Preference mPreference; + @Mock private Context mContext; + @Mock private PackageManager mPackageManager; + @Mock private UserManager mUserManager; + private ResolveInfo mFactoryResetAppResolveInfo; + private PackageInfo mFactoryResetAppPackageInfo; - private Context mContext; private FactoryResetPreferenceController mController; @Before - public void setUp() { - mContext = RuntimeEnvironment.application; - mShadowUserManager = ShadowUserManager.getShadow(); - + public void setUp() throws PackageManager.NameNotFoundException { + MockitoAnnotations.initMocks(this); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); mController = new FactoryResetPreferenceController(mContext, FACTORY_RESET_KEY); + mFactoryResetAppResolveInfo = new ResolveInfo(); + mFactoryResetAppResolveInfo.activityInfo = new ActivityInfo(); + mFactoryResetAppResolveInfo.activityInfo.packageName = FACTORY_RESET_APP_PACKAGE; + mFactoryResetAppPackageInfo = new PackageInfo(); + mFactoryResetAppPackageInfo.requestedPermissions = + new String[] {Manifest.permission.PREPARE_FACTORY_RESET}; + mFactoryResetAppPackageInfo.requestedPermissionsFlags = new int[] { + PackageInfo.REQUESTED_PERMISSION_GRANTED + }; + when(mPackageManager.resolveActivity(any(), anyInt())) + .thenReturn(mFactoryResetAppResolveInfo); + when(mPackageManager.getPackageInfo(anyString(), anyInt())) + .thenReturn(mFactoryResetAppPackageInfo); + when(mPreference.getKey()).thenReturn(FACTORY_RESET_KEY); + mController.mFactoryResetPreparationLauncher = mFactoryResetLauncher; + } @After public void tearDown() { - ShadowUtils.reset(); - mShadowUserManager.setIsAdminUser(false); - mShadowUserManager.setIsDemoUser(false); - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 0); + Mockito.reset(mUserManager, mPackageManager); + Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(), + Settings.Global.DEVICE_DEMO_MODE, 0); } @Ignore("b/314930928") @Test public void isAvailable_systemUser() { - mShadowUserManager.setIsAdminUser(true); + when(mUserManager.isAdminUser()).thenReturn(true); assertThat(mController.isAvailable()).isTrue(); } @Test public void isAvailable_nonSystemUser() { - mShadowUserManager.setIsAdminUser(false); - mShadowUserManager.setIsDemoUser(false); + when(mUserManager.isAdminUser()).thenReturn(false); + when(mUserManager.isDemoUser()).thenReturn(false); assertThat(mController.isAvailable()).isFalse(); } @Test public void isAvailable_demoUser() { - mShadowUserManager.setIsAdminUser(false); + when(mUserManager.isAdminUser()).thenReturn(false); // Place the device in demo mode. - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1); + Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(), + Settings.Global.DEVICE_DEMO_MODE, 1); // Indicate the user is a demo user. - mShadowUserManager.addUser(UserHandle.myUserId(), "test", UserInfo.FLAG_DEMO); + when(mUserManager.getUserProfiles()) + .thenReturn(ImmutableList.of(new UserHandle(UserHandle.myUserId()))); + when(mUserManager.getUserInfo(eq(UserHandle.myUserId()))) + .thenReturn(new UserInfo(UserHandle.myUserId(), "test", UserInfo.FLAG_DEMO)); assertThat(mController.isAvailable()).isFalse(); } @@ -94,4 +144,16 @@ public class FactoryResetPreferenceControllerTest { public void getPreferenceKey() { assertThat(mController.getPreferenceKey()).isEqualTo(FACTORY_RESET_KEY); } + + @Test + @RequiresFlagsEnabled(com.android.settings.factory_reset.Flags.FLAG_ENABLE_FACTORY_RESET_WIZARD) + public void handlePreference_factoryResetWizardEnabled() { + ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + + assertThat(mController.handlePreferenceTreeClick(mPreference)).isTrue(); + verify(mFactoryResetLauncher).launch(intentArgumentCaptor.capture()); + assertThat(intentArgumentCaptor.getValue()).isNotNull(); + assertThat(intentArgumentCaptor.getValue().getAction()) + .isEqualTo(FactoryResetPreferenceController.ACTION_PREPARE_FACTORY_RESET); + } } diff --git a/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowDeviceConfig.java b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowDeviceConfig.java index dfd09887e54..acb1dd8a9a7 100644 --- a/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowDeviceConfig.java +++ b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowDeviceConfig.java @@ -16,7 +16,6 @@ package com.android.settings.testutils.shadow; -import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; @@ -25,7 +24,7 @@ import java.util.HashMap; import java.util.Map; @Implements(android.provider.DeviceConfig.class) -public class ShadowDeviceConfig { +public class ShadowDeviceConfig extends org.robolectric.shadows.ShadowDeviceConfig { private static Map sPropertyMaps = new HashMap<>(); diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt index 6b4cc0d4a1f..2afb3f1ee62 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt @@ -136,8 +136,7 @@ class AppArchiveButtonTest { verify(packageInstaller).requestArchive( eq(PACKAGE_NAME), - any(), - eq(0) + any() ) }