Snap for 11517367 from 12108acc07 to 24Q2-release

Change-Id: I09b5a93f800b059e4821d1634e44aa7e5a0bccbf
This commit is contained in:
Android Build Coastguard Worker
2024-03-01 00:21:23 +00:00
36 changed files with 1029 additions and 190 deletions

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/keyboard_layout_picker_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/keyboard_picker_margin_one_pane"
android:layout_marginVertical="@dimen/keyboard_picker_margin_one_pane_large"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/keyboard_layout_preview_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/keyboard_picker_margin_one_pane"
android:background="@drawable/keyboard_review_layout_background">
<ImageView
android:id="@+id/keyboard_layout_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/keyboard_picker_margin_small"
android:layout_marginTop="@dimen/keyboard_picker_margin_small"
android:layout_marginBottom="@dimen/keyboard_picker_margin_large"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/keyboard_layout_preview_name"
android:layout_width="match_parent"
android:layout_height="@dimen/keyboard_picker_margin_large"
android:layout_gravity="bottom"
android:gravity="center"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/keyboard_picker_text_size" />
</FrameLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/keyboard_picker_margin_one_pane"
android:layout_weight="1"
android:orientation="vertical">
<FrameLayout
android:id="@+id/keyboard_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/colorBackground"
android:elevation="1dp"
android:outlineAmbientShadowColor="@android:color/transparent"
android:outlineSpotShadowColor="@android:color/transparent" />
<FrameLayout
android:id="@+id/keyboard_layouts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyboard_picker_margin_small"
android:background="?android:attr/colorBackground" />
</LinearLayout>
</LinearLayout>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -808,4 +808,8 @@
<!-- Array of carrier id to allow the pSIM conversion-->
<integer-array name="config_psim_conversion_menu_enabled_carrier" translatable="false">
</integer-array>
<!-- Array of carrier id that uses reusable activation code-->
<integer-array name="config_carrier_use_rac" translatable="false">
</integer-array>
</resources>

View File

@@ -168,6 +168,8 @@
<!-- Keyboard -->
<dimen name="keyboard_picker_margin_large">68dp</dimen>
<dimen name="keyboard_picker_margin">24dp</dimen>
<dimen name="keyboard_picker_margin_one_pane_large">48dp</dimen>
<dimen name="keyboard_picker_margin_one_pane">24dp</dimen>
<dimen name="keyboard_picker_margin_small">16dp</dimen>
<dimen name="keyboard_picker_radius">28dp</dimen>
<dimen name="keyboard_picker_text_size">16sp</dimen>

View File

@@ -2060,6 +2060,10 @@
<string name="wifi_settings_wep_networks_button_allow">Allow WEP</string>
<!-- Wi-Fi settings dialog. Button text of dialog displayed when WEP network toggle is blocked. [CHAR LIMIT=NONE] -->
<string name="wifi_settings_ssid_block_button_close">Close</string>
<!-- Wi-Fi settings dialog. Title of dialog displayed when the user turns off “Allow WEP networks” while connected to a WEP network. [CHAR LIMIT=NONE] -->
<string name="wifi_settings_wep_networks_disconnect_title">Disconnect from <xliff:g id="name">%1$s</xliff:g>?</string>
<!-- Wi-Fi settings dialog. Summary text of dialog displayed when the user turns off “Allow WEP networks” while connected to a WEP network. [CHAR LIMIT=NONE] -->
<string name="wifi_settings_wep_networks_disconnect_summary">You\u0027re connected to a WEP network. If you block these networks, you\u0027ll be disconnected.</string>
<!-- Dialog for Access Points --> <skip />
<!-- Label to show/hide advanced options [CHAR LIMIT=40] -->
@@ -5154,14 +5158,14 @@
<!-- Generic title for editing the shortcuts of multiple accessibility features. [CHAR LIMIT=NONE] -->
<string name="accessibility_shortcut_edit_screen_title">Edit accessibility shortcuts</string>
<!-- Prompt for editing the shortcuts of multiple accessibility features. [CHAR LIMIT=NONE] -->
<string name="accessibility_shortcut_edit_screen_prompt">Chose your shortcut for %1$s</string>
<string name="accessibility_shortcut_edit_screen_prompt">Choose your shortcut for %1$s</string>
<!-- Button text for the accessibility dialog continue to the next screen for hearing aid. [CHAR LIMIT=32] -->
<string name="accessibility_hearingaid_instruction_continue_button">Continue</string>
<!-- Title for the accessibility preference for hearing devices. [CHAR LIMIT=35] -->
<string name="accessibility_hearingaid_title">Hearing devices</string>
<!-- Introduction for the Hearing devices page to introduce feature. [CHAR LIMIT=NONE] -->
<string name="accessibility_hearingaid_intro">You can use hearing aids, cochlear implants, and other amplification devices with your phone</string>
<!-- Introduction for the Hearing devices page to introduce feature. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=5856992709195963850] -->
<string name="accessibility_hearingaid_intro">Set up and manage ASHA and LE Audio hearing aids, cochlear implants, and other amplification devices</string>
<!-- Summary for the accessibility preference for hearing aid when not connected. [CHAR LIMIT=50] -->
<string name="accessibility_hearingaid_not_connected_summary">No hearing devices connected</string>
<!-- Summary for the accessibility preference for hearing aid when adding new devices. [CHAR LIMIT=50] -->
@@ -5196,6 +5200,8 @@
<string name="accessibility_hac_mode_summary">Improves compatibility with telecoils and reduces unwanted noise</string>
<!-- Title for accessibility hearing device page footer. [CHAR LIMIT=40] -->
<string name="accessibility_hearing_device_about_title">About hearing devices</string>
<!-- Description for text in accessibility hearing aids footer. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=7451899224828040581] -->
<string name="accessibility_hearing_device_footer_summary">To find other hearing devices that arent supported by ASHA or LE Audio, tap <b>Pair new device</b> > <b>See more devices</b></string>
<!-- Title for the pair hearing device page. [CHAR LIMIT=25] -->
<string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string>
<!-- Subtitle for the pair hearing device page. [CHAR LIMIT=NONE] -->
@@ -12338,6 +12344,15 @@
<!-- Summary for UWB preference when UWB is unavailable due to regulatory requirements. [CHAR_LIMIT=NONE]-->
<string name="uwb_settings_summary_no_uwb_regulatory">UWB is unavailable in the current location</string>
<!-- Title for Thread network preference [CHAR_LIMIT=60] -->
<string name="thread_network_settings_title">Thread</string>
<!-- Summary for Thread network preference. [CHAR_LIMIT=NONE]-->
<string name="thread_network_settings_summary">Connect to compatible devices using Thread for a seamless smart home experience</string>
<!-- Summary for Thread network preference when airplane mode is enabled. [CHAR_LIMIT=NONE]-->
<string name="thread_network_settings_summary_airplane_mode">Turn off airplane mode to use Thread</string>
<!-- Label for the camera use toggle [CHAR LIMIT=40] -->
<string name="camera_toggle_title">Camera access</string>
<!-- Label for the camera use toggle [CHAR LIMIT=40] -->

View File

@@ -60,4 +60,11 @@
settings:searchable="true"
settings:controller="com.android.settings.accessibility.HearingAidCompatibilityPreferenceController"/>
</PreferenceCategory>
<com.android.settings.accessibility.AccessibilityFooterPreference
android:key="hearing_device_footer"
android:title="@string/accessibility_hearing_device_footer_summary"
android:selectable="false"
settings:searchable="false"
settings:controller="com.android.settings.accessibility.HearingDeviceFooterPreferenceController"/>
</PreferenceScreen>

View File

@@ -63,6 +63,15 @@
settings:useAdminDisabledSummary="true"
settings:userRestriction="no_ultra_wideband_radio" />
<com.android.settingslib.RestrictedSwitchPreference
android:key="thread_network_settings"
android:title="@string/thread_network_settings_title"
android:order="110"
android:summary="@string/summary_placeholder"
settings:controller="com.android.settings.connecteddevice.threadnetwork.ThreadNetworkPreferenceController"
settings:userRestriction="no_thread_network"
settings:useAdminDisabledSummary="true"/>
<PreferenceCategory
android:key="dashboard_tile_placeholder"
android:order="-8" />

View File

@@ -38,5 +38,4 @@
settings:useAdminDisabledSummary="true"
settings:controller="com.android.settings.accessibility.ViewAllBluetoothDevicesPreferenceController"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -16,6 +16,8 @@
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -40,8 +42,7 @@ public class ColorContrastFragment extends DashboardFragment {
@Override
public int getMetricsCategory() {
// TODO(b/326539398): Add metrics tracking for color contrast.
return 0;
return SettingsEnums.ACCESSIBILITY_COLOR_CONTRAST;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =

View File

@@ -563,7 +563,7 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
@StringRes
protected int getAgreeButtonTextRes() {
return R.string.security_settings_fingerprint_enroll_introduction_agree;
return R.string.security_settings_face_enroll_introduction_agree;
}
@Override

View File

@@ -0,0 +1 @@
include platform/packages/modules/Connectivity:/thread/OWNERS

View File

@@ -0,0 +1,236 @@
/*
* 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.connecteddevice.threadnetwork
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.net.thread.ThreadNetworkController
import android.net.thread.ThreadNetworkController.StateCallback
import android.net.thread.ThreadNetworkException
import android.net.thread.ThreadNetworkManager
import android.os.OutcomeReceiver
import android.provider.Settings
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.net.thread.platform.flags.Flags
import com.android.settings.R
import com.android.settings.core.TogglePreferenceController
import java.util.concurrent.Executor
/** Controller for the "Thread" toggle in "Connected devices > Connection preferences". */
class ThreadNetworkPreferenceController @VisibleForTesting constructor(
context: Context,
key: String,
private val executor: Executor,
private val threadController: BaseThreadNetworkController?
) : TogglePreferenceController(context, key), LifecycleEventObserver {
private val stateCallback: StateCallback
private val airplaneModeReceiver: BroadcastReceiver
private var threadEnabled = false
private var airplaneModeOn = false
private var preference: Preference? = null
/**
* A testable interface for [ThreadNetworkController] which is `final`.
*
* We are in a awkward situation that Android API guideline suggest `final` for API classes
* while Robolectric test is being deprecated for platform testing (See
* tests/robotests/new_tests_hook.sh). This force us to use "mockito-target-extended" but it's
* conflicting with the default "mockito-target" which is somehow indirectly depended by the
* `SettingsUnitTests` target.
*/
@VisibleForTesting
interface BaseThreadNetworkController {
fun setEnabled(
enabled: Boolean,
executor: Executor,
receiver: OutcomeReceiver<Void?, ThreadNetworkException>
)
fun registerStateCallback(executor: Executor, callback: StateCallback)
fun unregisterStateCallback(callback: StateCallback)
}
constructor(context: Context, key: String) : this(
context,
key,
ContextCompat.getMainExecutor(context),
getThreadNetworkController(context)
)
init {
stateCallback = newStateCallback()
airplaneModeReceiver = newAirPlaneModeReceiver()
}
val isThreadSupportedOnDevice: Boolean
get() = threadController != null
private fun newStateCallback(): StateCallback {
return object : StateCallback {
override fun onThreadEnableStateChanged(enabledState: Int) {
threadEnabled = enabledState == ThreadNetworkController.STATE_ENABLED
}
override fun onDeviceRoleChanged(role: Int) {}
}
}
private fun newAirPlaneModeReceiver(): BroadcastReceiver {
return object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
airplaneModeOn = isAirplaneModeOn(context)
Log.i(TAG, "Airplane mode is " + if (airplaneModeOn) "ON" else "OFF")
preference?.let { preference -> updateState(preference) }
}
}
}
override fun getAvailabilityStatus(): Int {
return if (!Flags.threadEnabledPlatform()) {
CONDITIONALLY_UNAVAILABLE
} else if (!isThreadSupportedOnDevice) {
UNSUPPORTED_ON_DEVICE
} else if (airplaneModeOn) {
DISABLED_DEPENDENT_SETTING
} else {
AVAILABLE
}
}
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
preference = screen.findPreference(preferenceKey)
}
override fun isChecked(): Boolean {
// TODO (b/322742298):
// Check airplane mode here because it's planned to disable Thread state in airplane mode
// (code in the mainline module). But it's currently not implemented yet (b/322742298).
// By design, the toggle should be unchecked in airplane mode, so explicitly check the
// airplane mode here to acchieve the same UX.
return !airplaneModeOn && threadEnabled
}
override fun setChecked(isChecked: Boolean): Boolean {
if (threadController == null) {
return false
}
val action = if (isChecked) "enable" else "disable"
threadController.setEnabled(
isChecked,
executor,
object : OutcomeReceiver<Void?, ThreadNetworkException> {
override fun onError(e: ThreadNetworkException) {
// TODO(b/327549838): gracefully handle the failure by resetting the UI state
Log.e(TAG, "Failed to $action Thread", e)
}
override fun onResult(unused: Void?) {
Log.d(TAG, "Successfully $action Thread")
}
})
return true
}
override fun onStateChanged(lifecycleOwner: LifecycleOwner, event: Lifecycle.Event) {
if (threadController == null) {
return
}
when (event) {
Lifecycle.Event.ON_START -> {
threadController.registerStateCallback(executor, stateCallback)
airplaneModeOn = isAirplaneModeOn(mContext)
mContext.registerReceiver(
airplaneModeReceiver,
IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
)
preference?.let { preference -> updateState(preference) }
}
Lifecycle.Event.ON_STOP -> {
threadController.unregisterStateCallback(stateCallback)
mContext.unregisterReceiver(airplaneModeReceiver)
}
else -> {}
}
}
override fun updateState(preference: Preference) {
super.updateState(preference)
preference.isEnabled = !airplaneModeOn
refreshSummary(preference)
}
override fun getSummary(): CharSequence {
val resId: Int = if (airplaneModeOn) {
R.string.thread_network_settings_summary_airplane_mode
} else {
R.string.thread_network_settings_summary
}
return mContext.getResources().getString(resId)
}
override fun getSliceHighlightMenuRes(): Int {
return R.string.menu_key_connected_devices
}
companion object {
private const val TAG = "ThreadNetworkSettings"
private fun getThreadNetworkController(context: Context): BaseThreadNetworkController? {
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_THREAD_NETWORK)) {
return null
}
val manager = context.getSystemService(ThreadNetworkManager::class.java) ?: return null
val controller = manager.allThreadNetworkControllers[0]
return object : BaseThreadNetworkController {
override fun setEnabled(
enabled: Boolean,
executor: Executor,
receiver: OutcomeReceiver<Void?, ThreadNetworkException>
) {
controller.setEnabled(enabled, executor, receiver)
}
override fun registerStateCallback(executor: Executor, callback: StateCallback) {
controller.registerStateCallback(executor, callback)
}
override fun unregisterStateCallback(callback: StateCallback) {
controller.unregisterStateCallback(callback)
}
}
}
private fun isAirplaneModeOn(context: Context): Boolean {
return Settings.Global.getInt(
context.contentResolver,
Settings.Global.AIRPLANE_MODE_ON,
0
) == 1
}
}
}

View File

@@ -187,12 +187,6 @@ public class SettingsHomepageActivity extends FragmentActivity implements
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!isTaskRoot() && !isSingleTask()) {
Log.i(TAG, "Not task root nor single task, finish");
finish();
return;
}
mIsEmbeddingActivityEnabled = ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this);
if (mIsEmbeddingActivityEnabled) {
final UserManager um = getSystemService(UserManager.class);
@@ -319,12 +313,6 @@ public class SettingsHomepageActivity extends FragmentActivity implements
updateHomepageUI();
}
private boolean isSingleTask() {
ActivityInfo info = getIntent().resolveActivityInfo(getPackageManager(),
PackageManager.MATCH_DEFAULT_ONLY);
return info.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;
}
private void updateSplitLayout() {
if (!mIsEmbeddingActivityEnabled) {
return;

View File

@@ -16,9 +16,11 @@
package com.android.settings.inputmethod;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.hardware.input.InputManager;
import android.hardware.input.KeyboardLayout;
@@ -35,6 +37,7 @@ import androidx.fragment.app.Fragment;
import com.android.hardware.input.Flags;
import com.android.settings.R;
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
//TODO: b/316243168 - [Physical Keyboard Setting] Refactor NewKeyboardLayoutPickerFragment
public class NewKeyboardLayoutPickerFragment extends Fragment {
@@ -81,7 +84,7 @@ public class NewKeyboardLayoutPickerFragment extends Fragment {
Bundle savedInstanceState) {
mInputManager = requireContext().getSystemService(InputManager.class);
ViewGroup fragmentView = (ViewGroup) inflater.inflate(
R.layout.keyboard_layout_picker, container, false);
getPickerLayout(getResources().getConfiguration()), container, false);
mKeyboardLayoutPreview = fragmentView.findViewById(R.id.keyboard_layout_preview);
mKeyboardLayoutPreviewText = fragmentView.findViewById(R.id.keyboard_layout_preview_name);
if (!Flags.keyboardLayoutPreviewFlag()) {
@@ -102,6 +105,12 @@ public class NewKeyboardLayoutPickerFragment extends Fragment {
return fragmentView;
}
private int getPickerLayout(Configuration configuration) {
return !ActivityEmbeddingUtils.isAlreadyEmbedded(this.getActivity())
&& configuration.orientation == ORIENTATION_LANDSCAPE
? R.layout.keyboard_layout_picker_one_pane_land : R.layout.keyboard_layout_picker;
}
private void updateViewMarginForPreviewFlagOff(ViewGroup fragmentView) {
LinearLayout previewContainer = fragmentView.findViewById(
R.id.keyboard_layout_picker_container);

View File

@@ -16,7 +16,6 @@
package com.android.settings.network
import android.app.ProgressDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
@@ -46,12 +45,12 @@ import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -59,12 +58,16 @@ import androidx.compose.ui.text.style.TextOverflow
import com.android.settings.R
import com.android.settings.SidecarFragment
import com.android.settings.network.telephony.SubscriptionActionDialogActivity
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.network.SimOnboardingPageProvider.getRoute
import com.android.settingslib.spa.SpaBaseDialogActivity
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.getDialogWidth
import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
import com.android.settingslib.spa.widget.ui.SettingsTitle
import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.CoroutineScope
@@ -78,8 +81,8 @@ import kotlinx.coroutines.launch
class SimOnboardingActivity : SpaBaseDialogActivity() {
lateinit var scope: CoroutineScope
lateinit var showBottomSheet: MutableState<Boolean>
lateinit var showError: MutableState<Boolean>
lateinit var showDialog: MutableState<Boolean>
lateinit var showError: MutableState<ErrorType>
lateinit var showProgressDialog: MutableState<Boolean>
private var switchToEuiccSubscriptionSidecar: SwitchToEuiccSubscriptionSidecar? = null
private var switchToRemovableSlotSidecar: SwitchToRemovableSlotSidecar? = null
@@ -101,13 +104,19 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
return
}
if (onboardingService.activeSubInfoList.isEmpty()) {
// TODO: refactor and replace the ToggleSubscriptionDialogActivity
Log.d(TAG, "onboardingService.activeSubInfoList is empty" +
", start ToggleSubscriptionDialogActivity")
this.startActivity(ToggleSubscriptionDialogActivity
.getIntent(this.applicationContext, targetSubId, true))
finish()
return
}
switchToEuiccSubscriptionSidecar = SwitchToEuiccSubscriptionSidecar.get(fragmentManager)
switchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(fragmentManager)
enableMultiSimSidecar = EnableMultiSimSidecar.get(fragmentManager)
setContent {
Content()
}
}
override fun finish() {
@@ -116,15 +125,14 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
super.finish()
}
var callbackListener: (Int) -> Unit = {
var callbackListener: (CallbackType) -> Unit = {
Log.d(TAG, "Receive the CALLBACK: $it")
when (it) {
CALLBACK_ERROR -> {
CallbackType.CALLBACK_ERROR -> {
setProgressDialog(false)
showError.value = true
}
CALLBACK_ONBOARDING_COMPLETE -> {
CallbackType.CALLBACK_ONBOARDING_COMPLETE -> {
showBottomSheet.value = false
setProgressDialog(true)
scope.launch {
@@ -134,26 +142,29 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
}
}
CALLBACK_SETUP_NAME -> {
CallbackType.CALLBACK_SETUP_NAME -> {
scope.launch {
onboardingService.startSetupName()
}
}
CALLBACK_SETUP_PRIMARY_SIM -> {
CallbackType.CALLBACK_SETUP_PRIMARY_SIM -> {
scope.launch {
onboardingService.startSetupPrimarySim(this@SimOnboardingActivity)
}
}
CALLBACK_FINISH -> {
CallbackType.CALLBACK_FINISH -> {
finish()
}
}
}
fun setProgressDialog(enable: Boolean) {
showDialog.value = enable
if (!this::showProgressDialog.isInitialized) {
return
}
showProgressDialog.value = enable
val progressState = if (enable) {
SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
} else {
@@ -165,16 +176,19 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
showBottomSheet = remember { mutableStateOf(true) }
showError = remember { mutableStateOf(false) }
showDialog = remember { mutableStateOf(false) }
showBottomSheet = remember { mutableStateOf(false) }
showError = remember { mutableStateOf(ErrorType.ERROR_NONE) }
showProgressDialog = remember { mutableStateOf(false) }
scope = rememberCoroutineScope()
registerSidecarReceiverFlow()
if(showError.value){
// show error
return
ErrorDialogImpl()
LaunchedEffect(Unit) {
if (onboardingService.activeSubInfoList.isNotEmpty()) {
showBottomSheet.value = true
}
}
if (showBottomSheet.value) {
@@ -195,7 +209,9 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
},
cancelAction = { finish() },
)
} else {
}
if(showProgressDialog.value) {
ProgressDialogImpl()
}
}
@@ -203,7 +219,6 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProgressDialogImpl() {
if(showDialog.value) {
// TODO: Create the SPA's ProgressDialog and using SPA's widget
BasicAlertDialog(
onDismissRequest = {},
@@ -235,6 +250,55 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
}
}
}
@Composable
fun ErrorDialogImpl(){
// EuiccSlotSidecar showErrorDialog
val errorDialogPresenterForEuiccSlotSidecar = rememberAlertDialogPresenter(
confirmButton = AlertDialogButton(
stringResource(android.R.string.ok)
) {
finish()
},
title = stringResource(R.string.privileged_action_disable_fail_title),
text = {
Text(stringResource(R.string.privileged_action_disable_fail_text))
},
)
// RemovableSlotSidecar showErrorDialog
val errorDialogPresenterForRemovableSlotSidecar = rememberAlertDialogPresenter(
confirmButton = AlertDialogButton(
stringResource(android.R.string.ok)
) {
finish()
},
title = stringResource(R.string.sim_action_enable_sim_fail_title),
text = {
Text(stringResource(R.string.sim_action_enable_sim_fail_text))
},
)
// enableDSDS showErrorDialog
val errorDialogPresenterForMultiSimSidecar = rememberAlertDialogPresenter(
confirmButton = AlertDialogButton(
stringResource(android.R.string.ok)
) {
finish()
},
title = stringResource(R.string.dsds_activation_failure_title),
text = {
Text(stringResource(R.string.dsds_activation_failure_body_msg2))
},
)
// show error
when (showError.value) {
ErrorType.ERROR_EUICC_SLOT -> errorDialogPresenterForEuiccSlotSidecar.open()
ErrorType.ERROR_REMOVABLE_SLOT -> errorDialogPresenterForRemovableSlotSidecar.open()
ErrorType.ERROR_ENABLE_DSDS -> errorDialogPresenterForMultiSimSidecar.open()
else -> {}
}
}
@Composable
@@ -304,13 +368,14 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
SidecarFragment.State.SUCCESS -> {
Log.i(TAG, "Successfully enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset()
callbackListener(CALLBACK_SETUP_NAME)
callbackListener(CallbackType.CALLBACK_SETUP_NAME)
}
SidecarFragment.State.ERROR -> {
Log.i(TAG, "Failed to enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset()
callbackListener(CALLBACK_ERROR)
showError.value = ErrorType.ERROR_EUICC_SLOT
callbackListener(CallbackType.CALLBACK_ERROR)
// TODO: showErrorDialog and using privileged_action_disable_fail_title and
// privileged_action_disable_fail_text
}
@@ -323,13 +388,14 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
Log.i(TAG, "Successfully switched to removable slot.")
switchToRemovableSlotSidecar!!.reset()
onboardingService.handleTogglePsimAction()
callbackListener(CALLBACK_SETUP_NAME)
callbackListener(CallbackType.CALLBACK_SETUP_NAME)
}
SidecarFragment.State.ERROR -> {
Log.e(TAG, "Failed switching to removable slot.")
switchToRemovableSlotSidecar!!.reset()
callbackListener(CALLBACK_ERROR)
showError.value = ErrorType.ERROR_REMOVABLE_SLOT
callbackListener(CallbackType.CALLBACK_ERROR)
// TODO: showErrorDialog and using sim_action_enable_sim_fail_title and
// sim_action_enable_sim_fail_text
}
@@ -347,7 +413,8 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
SidecarFragment.State.ERROR -> {
enableMultiSimSidecar!!.reset()
Log.i(TAG, "Failed to switch to DSDS without rebooting.")
callbackListener(CALLBACK_ERROR)
showError.value = ErrorType.ERROR_ENABLE_DSDS
callbackListener(CallbackType.CALLBACK_ERROR)
// TODO: showErrorDialog and using dsds_activation_failure_title and
// dsds_activation_failure_body_msg2
}
@@ -370,7 +437,7 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
}
Log.i(TAG, "DSDS enabled, start to enable pSIM profile.")
onboardingService.handleTogglePsimAction()
callbackListener(CALLBACK_FINISH)
callbackListener(CallbackType.CALLBACK_FINISH)
}
@Composable
@@ -426,7 +493,7 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
Log.i(TAG, "setProgressState:$state")
}
fun initServiceData(context: Context,targetSubId: Int, callback:(Int)->Unit) {
fun initServiceData(context: Context,targetSubId: Int, callback:(CallbackType)->Unit) {
onboardingService.initData(targetSubId, context,callback)
}
@@ -445,10 +512,20 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
var onboardingService:SimOnboardingService = SimOnboardingService()
const val TAG = "SimOnboardingActivity"
const val SUB_ID = "sub_id"
const val CALLBACK_ERROR = -1
const val CALLBACK_ONBOARDING_COMPLETE = 1
const val CALLBACK_SETUP_NAME = 2
const val CALLBACK_SETUP_PRIMARY_SIM = 3
const val CALLBACK_FINISH = 4
enum class ErrorType(val value:Int){
ERROR_NONE(-1),
ERROR_EUICC_SLOT(1),
ERROR_REMOVABLE_SLOT(2),
ERROR_ENABLE_DSDS(3)
}
enum class CallbackType(val value:Int){
CALLBACK_ERROR(-1),
CALLBACK_ONBOARDING_COMPLETE(1),
CALLBACK_SETUP_NAME(2),
CALLBACK_SETUP_PRIMARY_SIM(3),
CALLBACK_FINISH(4)
}
}
}

View File

@@ -23,6 +23,7 @@ import android.telephony.TelephonyManager
import android.telephony.UiccCardInfo
import android.telephony.UiccSlotInfo
import android.util.Log
import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
import com.android.settings.spa.network.setAutomaticData
import com.android.settings.spa.network.setDefaultData
import com.android.settings.spa.network.setDefaultSms
@@ -31,7 +32,6 @@ import com.android.settingslib.utils.ThreadUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private const val TAG = "SimOnboardingService"
private const val INVALID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -60,7 +60,7 @@ class SimOnboardingService {
.map { it.subscriptionId }
.firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
var callback: (Int) -> Unit = {}
var callback: (CallbackType) -> Unit = {}
var isMultipleEnabledProfilesSupported: Boolean = false
get() {
@@ -135,24 +135,24 @@ class SimOnboardingService {
userSelectedSubInfoList.clear()
}
fun initData(inputTargetSubId:Int,context: Context, callback: (Int) -> Unit) {
fun initData(inputTargetSubId: Int,
context: Context,
callback: (CallbackType) -> Unit) {
this.callback = callback
targetSubId = inputTargetSubId
subscriptionManager = context.getSystemService(SubscriptionManager::class.java)
telephonyManager = context.getSystemService(TelephonyManager::class.java)
Log.d(
TAG, "startInit: targetSubId:$targetSubId"
TAG, "startInit: targetSubId:$targetSubId, activeSubInfoList: $activeSubInfoList"
)
ThreadUtils.postOnBackgroundThread {
activeSubInfoList = SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
ThreadUtils.postOnBackgroundThread {
availableSubInfoList = SubscriptionUtil.getAvailableSubscriptions(context)
targetSubInfo =
availableSubInfoList.find { subInfo -> subInfo.subscriptionId == targetSubId }
targetSubInfo?.let { userSelectedSubInfoList.add(it) }
Log.d(
TAG, "targetSubId: $targetSubId" + ", targetSubInfo: $targetSubInfo" +
". activeSubInfoList: $activeSubInfoList"
)
Log.d(TAG, "targetSubId: $targetSubId , targetSubInfo: $targetSubInfo")
slotInfoList = telephonyManager?.uiccSlotsInfo?.toList() ?: listOf()
Log.d(TAG, "slotInfoList: $slotInfoList.")
uiccCardInfoList = telephonyManager?.uiccCardsInfo!!
@@ -196,6 +196,16 @@ class SimOnboardingService {
return userSelectedSubInfoList.toList()
}
fun getSelectedSubscriptionInfoListWithRenaming(): List<SubscriptionInfo> {
if (userSelectedSubInfoList.isEmpty()){
Log.d(TAG, "userSelectedSubInfoList is empty")
return activeSubInfoList
}
return userSelectedSubInfoList.map {
SubscriptionInfo.Builder(it).setDisplayName(getSubscriptionInfoDisplayName(it)).build()
}.toList()
}
fun addItemForRenaming(subInfo: SubscriptionInfo, newName: String) {
if (subInfo.displayName == newName) {
return
@@ -212,7 +222,11 @@ class SimOnboardingService {
}
fun addCurrentItemForSelectedSim() {
if (userSelectedSubInfoList.size < getActiveModemCount) {
userSelectedSubInfoList.addAll(activeSubInfoList)
Log.d(TAG, "addCurrentItemForSelectedSim: userSelectedSubInfoList:" +
", $userSelectedSubInfoList")
}
}
fun addItemForSelectedSim(selectedSubInfo: SubscriptionInfo) {
@@ -249,7 +263,7 @@ class SimOnboardingService {
fun startActivatingSim(){
// TODO: start to activate sim
callback(SimOnboardingActivity.CALLBACK_FINISH)
callback(CallbackType.CALLBACK_FINISH)
}
suspend fun startSetupName() {
@@ -261,7 +275,7 @@ class SimOnboardingService {
)
}
// next action is SETUP_PRIMARY_SIM
callback(SimOnboardingActivity.CALLBACK_SETUP_PRIMARY_SIM)
callback(CallbackType.CALLBACK_SETUP_PRIMARY_SIM)
}
}
@@ -290,7 +304,7 @@ class SimOnboardingService {
}
// no next action, send finish
callback(SimOnboardingActivity.CALLBACK_FINISH)
callback(CallbackType.CALLBACK_FINISH)
}
}
}

View File

@@ -49,9 +49,11 @@ import com.android.settings.flags.Flags;
import com.android.settings.network.helper.SelectableSubscriptions;
import com.android.settings.network.helper.SubscriptionAnnotation;
import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -557,14 +559,22 @@ public class SubscriptionUtil {
* @param context {@code Context}
* @param subId The id of subscription need to be deleted.
*/
public static void startDeleteEuiccSubscriptionDialogActivity(Context context, int subId) {
public static void startDeleteEuiccSubscriptionDialogActivity(Context context, int subId,
int carrierId) {
if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
Log.i(TAG, "Unable to delete subscription due to invalid subscription ID.");
return;
}
// TODO(b/325693582): Add verification if carrier is RAC and logic for new dialog
final int[] carriersThatUseRAC = context.getResources().getIntArray(
R.array.config_carrier_use_rac);
boolean isCarrierRac = Arrays.stream(carriersThatUseRAC).anyMatch(cid -> cid == carrierId);
if (isCarrierRac && !isConnectedToWifiOrDifferentSubId(context, subId)) {
context.startActivity(EuiccRacConnectivityDialogActivity.getIntent(context, subId));
} else {
context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
}
}
/**
* Finds and returns a subscription with a specific subscription ID.

View File

@@ -33,6 +33,7 @@ import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
class DeleteSimProfilePreferenceController(context: Context, preferenceKey: String) :
BasePreferenceController(context, preferenceKey) {
private var subscriptionId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
private var carrierId: Int = TelephonyManager.UNKNOWN_CARRIER_ID
private var subscriptionInfo: SubscriptionInfo? = null
private lateinit var preference: Preference
@@ -40,6 +41,9 @@ class DeleteSimProfilePreferenceController(context: Context, preferenceKey: Stri
this.subscriptionId = subscriptionId
subscriptionInfo = SubscriptionUtil.getAvailableSubscriptions(mContext)
.find { it.subscriptionId == subscriptionId && it.isEmbedded }
subscriptionInfo?.let {
carrierId = it.carrierId
}
}
override fun getAvailabilityStatus() = when (subscriptionInfo) {
@@ -67,7 +71,8 @@ class DeleteSimProfilePreferenceController(context: Context, preferenceKey: Stri
}
private fun deleteSim() {
SubscriptionUtil.startDeleteEuiccSubscriptionDialogActivity(mContext, subscriptionId)
SubscriptionUtil.startDeleteEuiccSubscriptionDialogActivity(mContext, subscriptionId,
carrierId)
// result handled in MobileNetworkSettings
}
}

View File

@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.internal.telephony.flags.Flags;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.network.CarrierConfigCache;
@@ -58,6 +59,11 @@ public class SatelliteSettingPreferenceController extends
@Override
public int getAvailabilityStatus(int subId) {
if (!Flags.carrierEnabledSatelliteFlag()) {
logd("getAvailabilityStatus() : carrierEnabledSatelliteFlag is disabled");
return UNSUPPORTED_ON_DEVICE;
}
final PersistableBundle carrierConfig = mCarrierConfigCache.getConfigForSubId(subId);
final boolean isSatelliteAttachSupported = carrierConfig.getBoolean(
CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL);

View File

@@ -37,6 +37,7 @@ import com.android.settings.media.MediaOutputIndicatorWorker;
import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputConstants;
@@ -94,7 +95,9 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
@VisibleForTesting
boolean isSupportEndItem() {
return getWorker() != null && getWorker().isBroadcastSupported()
return Flags.legacyLeAudioSharing()
&& getWorker() != null
&& getWorker().isBroadcastSupported()
&& (getWorker().isDeviceBroadcasting() || isConnectedBLEDevice());
}
@@ -114,7 +117,8 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
if (mPreference != null) {
if (mPreference.isMuted()) {
mPreference.updateContentDescription(
mContext.getString(R.string.volume_content_description_silent_mode,
mContext.getString(
R.string.volume_content_description_silent_mode,
mPreference.getTitle()));
} else {
mPreference.updateContentDescription(mPreference.getTitle());
@@ -134,10 +138,15 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
if (getWorker().isDeviceBroadcasting()) {
intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME);
intent.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
intent.putExtra(
MediaOutputConstants.EXTRA_PACKAGE_NAME,
getWorker().getActiveLocalMediaController().getPackageName());
pi = PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
pi =
PendingIntent.getBroadcast(
context,
0 /* requestCode */,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
final CachedBluetoothDevice bluetoothDevice =
@@ -147,14 +156,20 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
return null;
}
intent.setAction(ACTION_LAUNCH_BROADCAST_DIALOG);
intent.putExtra(BluetoothBroadcastDialog.KEY_APP_LABEL,
intent.putExtra(
BluetoothBroadcastDialog.KEY_APP_LABEL,
Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
intent.putExtra(BluetoothBroadcastDialog.KEY_DEVICE_ADDRESS,
bluetoothDevice.getAddress());
intent.putExtra(BluetoothBroadcastDialog.KEY_MEDIA_STREAMING, getWorker() != null
&& getWorker().getActiveLocalMediaController() != null);
intent.putExtra(
BluetoothBroadcastDialog.KEY_DEVICE_ADDRESS, bluetoothDevice.getAddress());
intent.putExtra(
BluetoothBroadcastDialog.KEY_MEDIA_STREAMING,
getWorker() != null && getWorker().getActiveLocalMediaController() != null);
pi = PendingIntent.getActivity(context, 0 /* requestCode */, intent,
pi =
PendingIntent.getActivity(
context,
0 /* requestCode */,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
@@ -164,8 +179,8 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
}
private IconCompat getBroadcastIcon(Context context) {
final Drawable drawable = context.getDrawable(
com.android.settingslib.R.drawable.settings_input_antenna);
final Drawable drawable =
context.getDrawable(com.android.settingslib.R.drawable.settings_input_antenna);
if (drawable != null) {
drawable.setTint(Utils.getColorAccentDefaultColor(context));
return Utils.createIconWithDrawable(drawable);

View File

@@ -136,9 +136,7 @@ public class SimDialogActivity extends FragmentActivity {
}
if (Flags.isDualSimOnboardingEnabled()
&& getProgressState() == SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
&& (dialogType == PREFERRED_PICK
|| dialogType == DATA_PICK
&& (dialogType == DATA_PICK
|| dialogType == CALLS_PICK
|| dialogType == SMS_PICK)) {
Log.d(TAG, "Finish the sim dialog since the sim onboarding is shown");

View File

@@ -24,6 +24,7 @@ import android.os.Bundle
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavHostController
import androidx.navigation.NavType
@@ -33,6 +34,7 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.android.settings.R
import com.android.settings.network.SimOnboardingActivity
import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
import com.android.settings.network.SimOnboardingService
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -92,7 +94,7 @@ fun PageImpl(onboardingService:SimOnboardingService,navHostController: NavHostCo
val context = LocalContext.current
var finishOnboarding: () -> Unit = {
context.getActivity()?.finish()
onboardingService.callback(SimOnboardingActivity.CALLBACK_FINISH)
onboardingService.callback(CallbackType.CALLBACK_FINISH)
}
NavHost(
@@ -101,10 +103,13 @@ fun PageImpl(onboardingService:SimOnboardingService,navHostController: NavHostCo
) {
composable(route = SimOnboardingScreen.LabelSim.name) {
val nextPage =
if (onboardingService.isMultipleEnabledProfilesSupported && onboardingService.isAllOfSlotAssigned) {
if (onboardingService.isMultipleEnabledProfilesSupported
&& onboardingService.isAllOfSlotAssigned) {
SimOnboardingScreen.SelectSim.name
} else {
LaunchedEffect(Unit) {
onboardingService.addCurrentItemForSelectedSim()
}
SimOnboardingScreen.PrimarySim.name
}
SimOnboardingLabelSimImpl(
@@ -116,7 +121,7 @@ fun PageImpl(onboardingService:SimOnboardingService,navHostController: NavHostCo
composable(route = SimOnboardingScreen.PrimarySim.name) {
SimOnboardingPrimarySimImpl(
nextAction = {
onboardingService.callback(SimOnboardingActivity.CALLBACK_ONBOARDING_COMPLETE)
onboardingService.callback(CallbackType.CALLBACK_ONBOARDING_COMPLETE)
context.getActivity()?.finish()
},
cancelAction = finishOnboarding,

View File

@@ -77,7 +77,8 @@ fun SimOnboardingPrimarySimImpl(
SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg))
}
var selectedSubscriptionInfoList = onboardingService.getSelectedSubscriptionInfoList()
var selectedSubscriptionInfoList =
onboardingService.getSelectedSubscriptionInfoListWithRenaming()
callsSelectedId.intValue = onboardingService.targetPrimarySimCalls
textsSelectedId.intValue = onboardingService.targetPrimarySimTexts
mobileDataSelectedId.intValue = onboardingService.targetPrimarySimMobileData

View File

@@ -18,24 +18,33 @@ package com.android.settings.wifi
import android.content.Context
import android.net.wifi.WifiManager
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.fragment.app.Fragment
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.framework.compose.OverridableFlow
import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.wifi.flags.Flags
import com.android.wifitrackerlib.WifiEntry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
/** Controller that controls whether the Wi-Fi Wakeup feature should be enabled. */
/** Controller that controls whether the WEP network can be connected. */
class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
ComposePreferenceController(context, preferenceKey) {
@@ -47,6 +56,8 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
@Composable
override fun Content() {
val checked by wepAllowedFlow.flow.collectAsStateWithLifecycle(initialValue = null)
var openDialog by rememberSaveable { mutableStateOf(false) }
val wifiInfo = wifiManager.connectionInfo
SwitchPreference(object : SwitchPreferenceModel {
override val title = stringResource(R.string.wifi_allow_wep_networks)
override val summary = { getSummary() }
@@ -54,10 +65,40 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
override val changeable: () -> Boolean
get() = { carrierAllowed }
override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
if (!newChecked && wifiInfo.currentSecurityType == WifiEntry.SECURITY_WEP) {
openDialog = true
} else {
wifiManager.setWepAllowed(newChecked)
wepAllowedFlow.override(newChecked)
}
}
})
if (openDialog) {
SettingsAlertDialogWithIcon(
onDismissRequest = { openDialog = false },
confirmButton = AlertDialogButton(
stringResource(R.string.wifi_disconnect_button_text)
) {
wifiManager.setWepAllowed(false)
wepAllowedFlow.override(false)
openDialog = false
},
dismissButton =
AlertDialogButton(
stringResource(R.string.wifi_cancel)
) { openDialog = false },
title = String.format(
stringResource(R.string.wifi_settings_wep_networks_disconnect_title),
wifiInfo.ssid.removeSurrounding("\"")
),
text = {
Text(
stringResource(R.string.wifi_settings_wep_networks_disconnect_summary),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
})
}
}
override fun getSummary(): String = mContext.getString(

View File

@@ -22,6 +22,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.UiModeManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -59,7 +60,8 @@ public class ColorContrastFragmentTest {
@Test
public void getMetricsCategory_returnsCorrectCategory() {
assertThat(mFragment.getMetricsCategory()).isEqualTo(0);
assertThat(mFragment.getMetricsCategory()).isEqualTo(
SettingsEnums.ACCESSIBILITY_COLOR_CONTRAST);
}
@Test

View File

@@ -18,6 +18,7 @@ package com.android.settings.applications.appinfo;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -25,10 +26,12 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.os.UserManager;
import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.SettingsActivity;
import com.android.settings.applications.ProcStatsData;
@@ -37,14 +40,14 @@ import com.android.settings.core.BasePreferenceController;
import com.android.settings.testutils.shadow.ShadowUserManager;
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.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
@@ -54,6 +57,8 @@ import org.robolectric.util.ReflectionHelpers;
com.android.settings.testutils.shadow.ShadowFragment.class,
})
public class AppMemoryPreferenceControllerTest {
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock
private SettingsActivity mActivity;
@@ -69,9 +74,11 @@ public class AppMemoryPreferenceControllerTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
ShadowUserManager.getShadow().setIsAdminUser(true);
mContext = spy(ApplicationProvider.getApplicationContext());
UserManager userManager = mock(UserManager.class);
when(userManager.isAdminUser()).thenReturn(true);
doReturn(userManager).when(mContext).getSystemService(Context.USER_SERVICE);
mController =
spy(new AppMemoryPreferenceController(mContext, mFragment, null /* lifecycle */));
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
@@ -80,7 +87,6 @@ public class AppMemoryPreferenceControllerTest {
when(mFragment.getActivity()).thenReturn(mActivity);
}
@Ignore("b/313582035")
@Test
@Config(qualifiers = "mcc999")
public void getAvailabilityStatus_developmentSettingsEnabled_shouldReturnAvailable() {

View File

@@ -29,7 +29,6 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -223,28 +222,6 @@ public class SettingsHomepageActivityTest {
& SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(0);
}
@Test
public void onCreate_notTaskRoot_shouldFinishActivity() {
SettingsHomepageActivity activity =
spy(Robolectric.buildActivity(SettingsHomepageActivity.class).get());
doReturn(false).when(activity).isTaskRoot();
activity.onCreate(/* savedInstanceState */ null);
verify(activity).finish();
}
@Test
public void onCreate_singleTaskActivity_shouldNotFinishActivity() {
SettingsHomepageActivity activity =
spy(Robolectric.buildActivity(DeepLinkHomepageActivity.class).get());
doReturn(false).when(activity).isTaskRoot();
activity.onCreate(/* savedInstanceState */ null);
verify(activity, never()).finish();
}
/** This test is for large screen devices Activity embedding. */
@Test
@Config(shadows = ShadowActivityEmbeddingUtils.class)

View File

@@ -31,17 +31,23 @@ import android.content.Intent;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.net.Uri;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.slice.builders.SliceAction;
import com.android.settings.media.MediaOutputIndicatorWorker;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputConstants;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -60,16 +66,16 @@ public class MediaVolumePreferenceControllerTest {
"android.settings.MEDIA_BROADCAST_DIALOG";
private static MediaOutputIndicatorWorker sMediaOutputIndicatorWorker;
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private MediaVolumePreferenceController mController;
private Context mContext;
@Mock
private MediaController mMediaController;
@Mock
private MediaDevice mDevice1;
@Mock
private MediaDevice mDevice2;
@Mock private MediaController mMediaController;
@Mock private MediaDevice mDevice1;
@Mock private MediaDevice mDevice2;
@Before
public void setUp() {
@@ -77,8 +83,8 @@ public class MediaVolumePreferenceControllerTest {
mContext = RuntimeEnvironment.application;
mController = new MediaVolumePreferenceController(mContext);
sMediaOutputIndicatorWorker = spy(
new MediaOutputIndicatorWorker(mContext, VOLUME_MEDIA_URI));
sMediaOutputIndicatorWorker =
spy(new MediaOutputIndicatorWorker(mContext, VOLUME_MEDIA_URI));
when(mDevice1.isBLEDevice()).thenReturn(true);
when(mDevice2.isBLEDevice()).thenReturn(false);
}
@@ -101,8 +107,8 @@ public class MediaVolumePreferenceControllerTest {
@Test
public void isSliceableCorrectKey_returnsTrue() {
final MediaVolumePreferenceController controller = new MediaVolumePreferenceController(
mContext);
final MediaVolumePreferenceController controller =
new MediaVolumePreferenceController(mContext);
assertThat(controller.isSliceable()).isTrue();
}
@@ -112,6 +118,17 @@ public class MediaVolumePreferenceControllerTest {
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_flagOff_returnsFalse() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
assertThat(mController.isSupportEndItem()).isFalse();
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_withBleDevice_returnsTrue() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -121,6 +138,7 @@ public class MediaVolumePreferenceControllerTest {
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_notSupportedBroadcast_returnsFalse() {
doReturn(false).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
@@ -129,6 +147,7 @@ public class MediaVolumePreferenceControllerTest {
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_withNonBleDevice_returnsFalse() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -138,6 +157,7 @@ public class MediaVolumePreferenceControllerTest {
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_deviceIsBroadcastingAndConnectedToNonBleDevice_returnsTrue() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -147,6 +167,7 @@ public class MediaVolumePreferenceControllerTest {
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_deviceIsNotBroadcastingAndConnectedToNonBleDevice_returnsFalse() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -155,8 +176,20 @@ public class MediaVolumePreferenceControllerTest {
assertThat(mController.isSupportEndItem()).isFalse();
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getSliceEndItem_flagOff_getsNullSliceAction() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
doReturn(mDevice2).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
final SliceAction sliceAction = mController.getSliceEndItem(mContext);
assertThat(sliceAction).isNull();
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getSliceEndItem_NotSupportEndItem_getsNullSliceAction() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -168,22 +201,26 @@ public class MediaVolumePreferenceControllerTest {
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getSliceEndItem_deviceIsBroadcasting_getsBroadcastIntent() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
doReturn(mMediaController)
.when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
final SliceAction sliceAction = mController.getSliceEndItem(mContext);
final PendingIntent endItemPendingIntent = sliceAction.getAction();
final PendingIntent expectedToggleIntent = getBroadcastIntent(
final PendingIntent expectedToggleIntent =
getBroadcastIntent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
assertThat(endItemPendingIntent).isEqualTo(expectedToggleIntent);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getSliceEndItem_deviceIsNotBroadcasting_getsActivityIntent() {
final MediaDevice device = mock(BluetoothMediaDevice.class);
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
@@ -192,7 +229,8 @@ public class MediaVolumePreferenceControllerTest {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(device).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
doReturn(mMediaController)
.when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
final SliceAction sliceAction = mController.getSliceEndItem(mContext);
@@ -215,13 +253,19 @@ public class MediaVolumePreferenceControllerTest {
private PendingIntent getBroadcastIntent(String action) {
final Intent intent = new Intent(action);
intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME);
return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent,
return PendingIntent.getBroadcast(
mContext,
0 /* requestCode */,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
private PendingIntent getActivityIntent(String action) {
final Intent intent = new Intent(action);
return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent,
return PendingIntent.getActivity(
mContext,
0 /* requestCode */,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
}

View File

@@ -34,7 +34,6 @@ import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowUserManager;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
@@ -44,7 +43,6 @@ import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@Ignore("b/315133235")
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowUserManager.class, ShadowDevicePolicyManager.class})
public class RestrictedButtonTest {

View File

@@ -35,18 +35,19 @@ import android.widget.ImageView;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceViewHolder;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowSettingsMediaPlayer;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.androidx.fragment.FragmentController;
@@ -55,7 +56,8 @@ import org.robolectric.shadows.androidx.fragment.FragmentController;
public class VideoPreferenceTest {
private static final int VIDEO_WIDTH = 100;
private static final int VIDEO_HEIGHT = 150;
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private VideoPreference.AnimationController mAnimationController;
@Mock
private ImageView fakePreview;
@@ -68,9 +70,7 @@ public class VideoPreferenceTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mContext = ApplicationProvider.getApplicationContext();
mAnimationController = spy(
new MediaAnimationController(mContext, R.raw.sample_video));
mVideoPreference = new VideoPreference(mContext, null /* attrs */);
@@ -141,7 +141,6 @@ public class VideoPreferenceTest {
assertThat(mAnimationController.isPlaying()).isTrue();
}
@Ignore("b/315133235")
@Test
@Config(qualifiers = "mcc999")
public void onViewVisible_createAnimationController() {

View File

@@ -20,6 +20,8 @@ import android.content.Context
import android.net.wifi.WifiManager
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.isDisplayed
import androidx.compose.ui.test.isNotDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
@@ -28,12 +30,16 @@ import androidx.preference.PreferenceManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settings.dashboard.DashboardFragment
import com.android.settings.spa.preference.ComposePreference
import com.android.settingslib.spa.testutils.onDialogText
import com.android.wifitrackerlib.WifiEntry
import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
@@ -48,25 +54,30 @@ class WepNetworksPreferenceControllerTest {
private var wepAllowed = true
private val mockWifiManager = mock<WifiManager> {
private var mockWifiInfo = mock<android.net.wifi.WifiInfo> {
on { it.currentSecurityType } doReturn WifiEntry.SECURITY_EAP
on { it.ssid } doReturn SSID
}
private var mockWifiManager = mock<WifiManager> {
on { queryWepAllowed(any(), any()) } doAnswer {
@Suppress("UNCHECKED_CAST")
val consumer = it.arguments[1] as Consumer<Boolean>
consumer.accept(wepAllowed)
}
on { it.isWepSupported } doReturn true
on { it.connectionInfo } doReturn mockWifiInfo
}
private var context: Context =
spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(WifiManager::class.java) } doReturn mockWifiManager
}
private var controller = WepNetworksPreferenceController(context, TEST_KEY)
private var controller = WepNetworksPreferenceController(context, TEST_KEY)
private val preference = ComposePreference(context).apply { key = TEST_KEY }
private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
@Before
fun setUp() {
preferenceScreen.addPreference(preference)
@@ -79,6 +90,7 @@ class WepNetworksPreferenceControllerTest {
composeTestRule.setContent {
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOn()
}
@@ -89,6 +101,7 @@ class WepNetworksPreferenceControllerTest {
composeTestRule.setContent {
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOff()
}
@@ -101,7 +114,6 @@ class WepNetworksPreferenceControllerTest {
}
composeTestRule.onRoot().performClick()
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOn()
}
@@ -114,12 +126,38 @@ class WepNetworksPreferenceControllerTest {
}
composeTestRule.onRoot().performClick()
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOff()
}
@Test
fun whenClick_wepAllowed_openDialog() {
wepAllowed = true
Mockito.`when`(mockWifiInfo.currentSecurityType).thenReturn(WifiEntry.SECURITY_WEP)
composeTestRule.setContent {
controller.Content()
}
composeTestRule.onRoot().performClick()
composeTestRule.onDialogText(context.getString(R.string.wifi_disconnect_button_text))
.isDisplayed()
}
@Test
fun whenClick_wepDisallowed_openDialog() {
wepAllowed = false
Mockito.`when`(mockWifiInfo.currentSecurityType).thenReturn(WifiEntry.SECURITY_WEP)
composeTestRule.setContent {
controller.Content()
}
composeTestRule.onRoot().performClick()
composeTestRule.onDialogText(context.getString(R.string.wifi_disconnect_button_text))
.isNotDisplayed()
}
private companion object {
const val TEST_KEY = "test_key"
const val SSID = "ssid"
}
}

View File

@@ -26,6 +26,7 @@ android_test {
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.preference_preference",
"flag-junit",
"mockito-target-minus-junit4",
"platform-test-annotations",
"platform-test-rules",

View File

@@ -0,0 +1 @@
include platform/packages/modules/Connectivity:/thread/OWNERS

View File

@@ -0,0 +1,255 @@
/*
* 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.connecteddevice.threadnetwork
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.thread.ThreadNetworkController.STATE_DISABLED
import android.net.thread.ThreadNetworkController.STATE_DISABLING
import android.net.thread.ThreadNetworkController.STATE_ENABLED
import android.net.thread.ThreadNetworkController.StateCallback
import android.net.thread.ThreadNetworkException
import android.os.OutcomeReceiver
import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceManager
import androidx.preference.SwitchPreference
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.net.thread.platform.flags.Flags
import com.android.settings.R
import com.android.settings.core.BasePreferenceController.AVAILABLE
import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE
import com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING
import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE
import com.android.settings.connecteddevice.threadnetwork.ThreadNetworkPreferenceController.BaseThreadNetworkController
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import java.util.concurrent.Executor
/** Unit tests for [ThreadNetworkPreferenceController]. */
@RunWith(AndroidJUnit4::class)
class ThreadNetworkPreferenceControllerTest {
@get:Rule
val mSetFlagsRule = SetFlagsRule()
private lateinit var context: Context
private lateinit var executor: Executor
private lateinit var controller: ThreadNetworkPreferenceController
private lateinit var fakeThreadNetworkController: FakeThreadNetworkController
private lateinit var preference: SwitchPreference
private val broadcastReceiverArgumentCaptor = ArgumentCaptor.forClass(
BroadcastReceiver::class.java
)
@Before
fun setUp() {
mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_ENABLED_PLATFORM)
context = spy(ApplicationProvider.getApplicationContext<Context>())
executor = ContextCompat.getMainExecutor(context)
fakeThreadNetworkController = FakeThreadNetworkController(executor)
controller = newControllerWithThreadFeatureSupported(true)
val preferenceManager = PreferenceManager(context)
val preferenceScreen = preferenceManager.createPreferenceScreen(context)
preference = SwitchPreference(context)
preference.key = "thread_network_settings"
preferenceScreen.addPreference(preference)
controller.displayPreference(preferenceScreen)
Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
}
private fun newControllerWithThreadFeatureSupported(
present: Boolean
): ThreadNetworkPreferenceController {
return ThreadNetworkPreferenceController(
context,
"thread_network_settings" /* key */,
executor,
if (present) fakeThreadNetworkController else null
)
}
@Test
fun availabilityStatus_flagDisabled_returnsConditionallyUnavailable() {
mSetFlagsRule.disableFlags(Flags.FLAG_THREAD_ENABLED_PLATFORM)
assertThat(controller.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE)
}
@Test
fun availabilityStatus_airPlaneModeOn_returnsDisabledDependentSetting() {
Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1)
controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
assertThat(controller.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING)
}
@Test
fun availabilityStatus_airPlaneModeOff_returnsAvailable() {
Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE)
}
@Test
fun availabilityStatus_threadFeatureNotSupported_returnsUnsupported() {
controller = newControllerWithThreadFeatureSupported(false)
controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
assertThat(fakeThreadNetworkController.registeredStateCallback).isNull()
assertThat(controller.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE)
}
@Test
fun isChecked_threadSetEnabled_returnsTrue() {
fakeThreadNetworkController.setEnabled(true, executor) { }
controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
assertThat(controller.isChecked).isTrue()
}
@Test
fun isChecked_threadSetDisabled_returnsFalse() {
fakeThreadNetworkController.setEnabled(false, executor) { }
controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
assertThat(controller.isChecked).isFalse()
}
@Test
fun setChecked_setChecked_threadIsEnabled() {
controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
controller.setChecked(true)
assertThat(fakeThreadNetworkController.isEnabled).isTrue()
}
@Test
fun setChecked_setUnchecked_threadIsDisabled() {
controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
controller.setChecked(false)
assertThat(fakeThreadNetworkController.isEnabled).isFalse()
}
@Test
fun updatePreference_airPlaneModeOff_preferenceEnabled() {
Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
assertThat(preference.isEnabled).isTrue()
assertThat(preference.summary).isEqualTo(
context.resources.getString(R.string.thread_network_settings_summary)
)
}
@Test
fun updatePreference_airPlaneModeOn_preferenceDisabled() {
Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1)
controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
assertThat(preference.isEnabled).isFalse()
assertThat(preference.summary).isEqualTo(
context.resources.getString(R.string.thread_network_settings_summary_airplane_mode)
)
}
@Test
fun updatePreference_airPlaneModeTurnedOn_preferenceDisabled() {
Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
startControllerAndCaptureCallbacks()
Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1)
broadcastReceiverArgumentCaptor.value.onReceive(context, Intent())
assertThat(preference.isEnabled).isFalse()
assertThat(preference.summary).isEqualTo(
context.resources.getString(R.string.thread_network_settings_summary_airplane_mode)
)
}
private fun startControllerAndCaptureCallbacks() {
controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
verify(context)!!.registerReceiver(broadcastReceiverArgumentCaptor.capture(), any())
}
private class FakeThreadNetworkController(private val executor: Executor) :
BaseThreadNetworkController {
var isEnabled = true
private set
var registeredStateCallback: StateCallback? = null
private set
override fun setEnabled(
enabled: Boolean,
executor: Executor,
receiver: OutcomeReceiver<Void?, ThreadNetworkException>
) {
isEnabled = enabled
if (registeredStateCallback != null) {
if (!isEnabled) {
executor.execute {
registeredStateCallback!!.onThreadEnableStateChanged(
STATE_DISABLING
)
}
executor.execute {
registeredStateCallback!!.onThreadEnableStateChanged(
STATE_DISABLED
)
}
} else {
executor.execute {
registeredStateCallback!!.onThreadEnableStateChanged(
STATE_ENABLED
)
}
}
}
executor.execute { receiver.onResult(null) }
}
override fun registerStateCallback(
executor: Executor,
callback: StateCallback
) {
require(callback !== registeredStateCallback) { "callback is already registered" }
registeredStateCallback = callback
val enabledState =
if (isEnabled) STATE_ENABLED else STATE_DISABLED
executor.execute { registeredStateCallback!!.onThreadEnableStateChanged(enabledState) }
}
override fun unregisterStateCallback(callback: StateCallback) {
requireNotNull(registeredStateCallback) { "callback is already unregistered" }
registeredStateCallback = null
}
}
}