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--> <!-- Array of carrier id to allow the pSIM conversion-->
<integer-array name="config_psim_conversion_menu_enabled_carrier" translatable="false"> <integer-array name="config_psim_conversion_menu_enabled_carrier" translatable="false">
</integer-array> </integer-array>
<!-- Array of carrier id that uses reusable activation code-->
<integer-array name="config_carrier_use_rac" translatable="false">
</integer-array>
</resources> </resources>

View File

@@ -168,6 +168,8 @@
<!-- Keyboard --> <!-- Keyboard -->
<dimen name="keyboard_picker_margin_large">68dp</dimen> <dimen name="keyboard_picker_margin_large">68dp</dimen>
<dimen name="keyboard_picker_margin">24dp</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_margin_small">16dp</dimen>
<dimen name="keyboard_picker_radius">28dp</dimen> <dimen name="keyboard_picker_radius">28dp</dimen>
<dimen name="keyboard_picker_text_size">16sp</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> <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] --> <!-- 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> <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 /> <!-- Dialog for Access Points --> <skip />
<!-- Label to show/hide advanced options [CHAR LIMIT=40] --> <!-- 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] --> <!-- Generic title for editing the shortcuts of multiple accessibility features. [CHAR LIMIT=NONE] -->
<string name="accessibility_shortcut_edit_screen_title">Edit accessibility shortcuts</string> <string name="accessibility_shortcut_edit_screen_title">Edit accessibility shortcuts</string>
<!-- Prompt for editing the shortcuts of multiple accessibility features. [CHAR LIMIT=NONE] --> <!-- 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] --> <!-- 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> <string name="accessibility_hearingaid_instruction_continue_button">Continue</string>
<!-- Title for the accessibility preference for hearing devices. [CHAR LIMIT=35] --> <!-- Title for the accessibility preference for hearing devices. [CHAR LIMIT=35] -->
<string name="accessibility_hearingaid_title">Hearing devices</string> <string name="accessibility_hearingaid_title">Hearing devices</string>
<!-- Introduction for the Hearing devices page to introduce feature. [CHAR LIMIT=NONE] --> <!-- Introduction for the Hearing devices page to introduce feature. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=5856992709195963850] -->
<string name="accessibility_hearingaid_intro">You can use hearing aids, cochlear implants, and other amplification devices with your phone</string> <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] --> <!-- 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> <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] --> <!-- 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> <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] --> <!-- Title for accessibility hearing device page footer. [CHAR LIMIT=40] -->
<string name="accessibility_hearing_device_about_title">About hearing devices</string> <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] --> <!-- Title for the pair hearing device page. [CHAR LIMIT=25] -->
<string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string> <string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string>
<!-- Subtitle for the pair hearing device page. [CHAR LIMIT=NONE] --> <!-- 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]--> <!-- 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> <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] --> <!-- Label for the camera use toggle [CHAR LIMIT=40] -->
<string name="camera_toggle_title">Camera access</string> <string name="camera_toggle_title">Camera access</string>
<!-- Label for the camera use toggle [CHAR LIMIT=40] --> <!-- Label for the camera use toggle [CHAR LIMIT=40] -->

View File

@@ -60,4 +60,11 @@
settings:searchable="true" settings:searchable="true"
settings:controller="com.android.settings.accessibility.HearingAidCompatibilityPreferenceController"/> settings:controller="com.android.settings.accessibility.HearingAidCompatibilityPreferenceController"/>
</PreferenceCategory> </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> </PreferenceScreen>

View File

@@ -63,6 +63,15 @@
settings:useAdminDisabledSummary="true" settings:useAdminDisabledSummary="true"
settings:userRestriction="no_ultra_wideband_radio" /> 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 <PreferenceCategory
android:key="dashboard_tile_placeholder" android:key="dashboard_tile_placeholder"
android:order="-8" /> android:order="-8" />

View File

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

View File

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

View File

@@ -563,7 +563,7 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
@Override @Override
@StringRes @StringRes
protected int getAgreeButtonTextRes() { protected int getAgreeButtonTextRes() {
return R.string.security_settings_fingerprint_enroll_introduction_agree; return R.string.security_settings_face_enroll_introduction_agree;
} }
@Override @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) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (!isTaskRoot() && !isSingleTask()) {
Log.i(TAG, "Not task root nor single task, finish");
finish();
return;
}
mIsEmbeddingActivityEnabled = ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this); mIsEmbeddingActivityEnabled = ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this);
if (mIsEmbeddingActivityEnabled) { if (mIsEmbeddingActivityEnabled) {
final UserManager um = getSystemService(UserManager.class); final UserManager um = getSystemService(UserManager.class);
@@ -319,12 +313,6 @@ public class SettingsHomepageActivity extends FragmentActivity implements
updateHomepageUI(); updateHomepageUI();
} }
private boolean isSingleTask() {
ActivityInfo info = getIntent().resolveActivityInfo(getPackageManager(),
PackageManager.MATCH_DEFAULT_ONLY);
return info.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;
}
private void updateSplitLayout() { private void updateSplitLayout() {
if (!mIsEmbeddingActivityEnabled) { if (!mIsEmbeddingActivityEnabled) {
return; return;

View File

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

View File

@@ -16,7 +16,6 @@
package com.android.settings.network package com.android.settings.network
import android.app.ProgressDialog
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@@ -46,12 +45,12 @@ import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign 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.R
import com.android.settings.SidecarFragment import com.android.settings.SidecarFragment
import com.android.settings.network.telephony.SubscriptionActionDialogActivity 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.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.network.SimOnboardingPageProvider.getRoute import com.android.settings.spa.network.SimOnboardingPageProvider.getRoute
import com.android.settingslib.spa.SpaBaseDialogActivity import com.android.settingslib.spa.SpaBaseDialogActivity
import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle 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.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.spa.widget.ui.SettingsTitle
import com.android.settingslib.spaprivileged.framework.common.userManager import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -78,8 +81,8 @@ import kotlinx.coroutines.launch
class SimOnboardingActivity : SpaBaseDialogActivity() { class SimOnboardingActivity : SpaBaseDialogActivity() {
lateinit var scope: CoroutineScope lateinit var scope: CoroutineScope
lateinit var showBottomSheet: MutableState<Boolean> lateinit var showBottomSheet: MutableState<Boolean>
lateinit var showError: MutableState<Boolean> lateinit var showError: MutableState<ErrorType>
lateinit var showDialog: MutableState<Boolean> lateinit var showProgressDialog: MutableState<Boolean>
private var switchToEuiccSubscriptionSidecar: SwitchToEuiccSubscriptionSidecar? = null private var switchToEuiccSubscriptionSidecar: SwitchToEuiccSubscriptionSidecar? = null
private var switchToRemovableSlotSidecar: SwitchToRemovableSlotSidecar? = null private var switchToRemovableSlotSidecar: SwitchToRemovableSlotSidecar? = null
@@ -101,13 +104,19 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
return 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) switchToEuiccSubscriptionSidecar = SwitchToEuiccSubscriptionSidecar.get(fragmentManager)
switchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(fragmentManager) switchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(fragmentManager)
enableMultiSimSidecar = EnableMultiSimSidecar.get(fragmentManager) enableMultiSimSidecar = EnableMultiSimSidecar.get(fragmentManager)
setContent {
Content()
}
} }
override fun finish() { override fun finish() {
@@ -116,15 +125,14 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
super.finish() super.finish()
} }
var callbackListener: (Int) -> Unit = { var callbackListener: (CallbackType) -> Unit = {
Log.d(TAG, "Receive the CALLBACK: $it") Log.d(TAG, "Receive the CALLBACK: $it")
when (it) { when (it) {
CALLBACK_ERROR -> { CallbackType.CALLBACK_ERROR -> {
setProgressDialog(false) setProgressDialog(false)
showError.value = true
} }
CALLBACK_ONBOARDING_COMPLETE -> { CallbackType.CALLBACK_ONBOARDING_COMPLETE -> {
showBottomSheet.value = false showBottomSheet.value = false
setProgressDialog(true) setProgressDialog(true)
scope.launch { scope.launch {
@@ -134,26 +142,29 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
} }
} }
CALLBACK_SETUP_NAME -> { CallbackType.CALLBACK_SETUP_NAME -> {
scope.launch { scope.launch {
onboardingService.startSetupName() onboardingService.startSetupName()
} }
} }
CALLBACK_SETUP_PRIMARY_SIM -> { CallbackType.CALLBACK_SETUP_PRIMARY_SIM -> {
scope.launch { scope.launch {
onboardingService.startSetupPrimarySim(this@SimOnboardingActivity) onboardingService.startSetupPrimarySim(this@SimOnboardingActivity)
} }
} }
CALLBACK_FINISH -> { CallbackType.CALLBACK_FINISH -> {
finish() finish()
} }
} }
} }
fun setProgressDialog(enable: Boolean) { fun setProgressDialog(enable: Boolean) {
showDialog.value = enable if (!this::showProgressDialog.isInitialized) {
return
}
showProgressDialog.value = enable
val progressState = if (enable) { val progressState = if (enable) {
SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
} else { } else {
@@ -165,16 +176,19 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun Content() { override fun Content() {
showBottomSheet = remember { mutableStateOf(true) } showBottomSheet = remember { mutableStateOf(false) }
showError = remember { mutableStateOf(false) } showError = remember { mutableStateOf(ErrorType.ERROR_NONE) }
showDialog = remember { mutableStateOf(false) } showProgressDialog = remember { mutableStateOf(false) }
scope = rememberCoroutineScope() scope = rememberCoroutineScope()
registerSidecarReceiverFlow() registerSidecarReceiverFlow()
if(showError.value){ ErrorDialogImpl()
// show error
return LaunchedEffect(Unit) {
if (onboardingService.activeSubInfoList.isNotEmpty()) {
showBottomSheet.value = true
}
} }
if (showBottomSheet.value) { if (showBottomSheet.value) {
@@ -195,7 +209,9 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
}, },
cancelAction = { finish() }, cancelAction = { finish() },
) )
} else { }
if(showProgressDialog.value) {
ProgressDialogImpl() ProgressDialogImpl()
} }
} }
@@ -203,40 +219,88 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ProgressDialogImpl() { fun ProgressDialogImpl() {
if(showDialog.value) { // TODO: Create the SPA's ProgressDialog and using SPA's widget
// TODO: Create the SPA's ProgressDialog and using SPA's widget BasicAlertDialog(
BasicAlertDialog( onDismissRequest = {},
onDismissRequest = {}, modifier = Modifier.width(
modifier = Modifier.width( getDialogWidth()
getDialogWidth() ),
), ) {
Surface(
color = AlertDialogDefaults.containerColor,
shape = AlertDialogDefaults.shape
) { ) {
Surface( Row(
color = AlertDialogDefaults.containerColor, modifier = Modifier
shape = AlertDialogDefaults.shape
) {
Row(
modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(SettingsDimension.itemPaddingStart), .padding(SettingsDimension.itemPaddingStart),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
CircularProgressIndicator() CircularProgressIndicator()
Column(modifier = Modifier Column(modifier = Modifier
.padding(start = SettingsDimension.itemPaddingStart)) { .padding(start = SettingsDimension.itemPaddingStart)) {
SettingsTitle( SettingsTitle(
stringResource( stringResource(
R.string.sim_onboarding_progressbar_turning_sim_on, R.string.sim_onboarding_progressbar_turning_sim_on,
onboardingService.targetSubInfo?.displayName ?: "" onboardingService.targetSubInfo?.displayName ?: ""
)
) )
} )
} }
} }
} }
} }
} }
@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 @Composable
fun registerSidecarReceiverFlow(){ fun registerSidecarReceiverFlow(){
switchToEuiccSubscriptionSidecar?.sidecarReceiverFlow() switchToEuiccSubscriptionSidecar?.sidecarReceiverFlow()
@@ -304,13 +368,14 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
SidecarFragment.State.SUCCESS -> { SidecarFragment.State.SUCCESS -> {
Log.i(TAG, "Successfully enable the eSIM profile.") Log.i(TAG, "Successfully enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset() switchToEuiccSubscriptionSidecar!!.reset()
callbackListener(CALLBACK_SETUP_NAME) callbackListener(CallbackType.CALLBACK_SETUP_NAME)
} }
SidecarFragment.State.ERROR -> { SidecarFragment.State.ERROR -> {
Log.i(TAG, "Failed to enable the eSIM profile.") Log.i(TAG, "Failed to enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset() 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 // TODO: showErrorDialog and using privileged_action_disable_fail_title and
// privileged_action_disable_fail_text // privileged_action_disable_fail_text
} }
@@ -323,13 +388,14 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
Log.i(TAG, "Successfully switched to removable slot.") Log.i(TAG, "Successfully switched to removable slot.")
switchToRemovableSlotSidecar!!.reset() switchToRemovableSlotSidecar!!.reset()
onboardingService.handleTogglePsimAction() onboardingService.handleTogglePsimAction()
callbackListener(CALLBACK_SETUP_NAME) callbackListener(CallbackType.CALLBACK_SETUP_NAME)
} }
SidecarFragment.State.ERROR -> { SidecarFragment.State.ERROR -> {
Log.e(TAG, "Failed switching to removable slot.") Log.e(TAG, "Failed switching to removable slot.")
switchToRemovableSlotSidecar!!.reset() 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 // TODO: showErrorDialog and using sim_action_enable_sim_fail_title and
// sim_action_enable_sim_fail_text // sim_action_enable_sim_fail_text
} }
@@ -347,7 +413,8 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
SidecarFragment.State.ERROR -> { SidecarFragment.State.ERROR -> {
enableMultiSimSidecar!!.reset() enableMultiSimSidecar!!.reset()
Log.i(TAG, "Failed to switch to DSDS without rebooting.") 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 // TODO: showErrorDialog and using dsds_activation_failure_title and
// dsds_activation_failure_body_msg2 // dsds_activation_failure_body_msg2
} }
@@ -370,7 +437,7 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
} }
Log.i(TAG, "DSDS enabled, start to enable pSIM profile.") Log.i(TAG, "DSDS enabled, start to enable pSIM profile.")
onboardingService.handleTogglePsimAction() onboardingService.handleTogglePsimAction()
callbackListener(CALLBACK_FINISH) callbackListener(CallbackType.CALLBACK_FINISH)
} }
@Composable @Composable
@@ -426,7 +493,7 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
Log.i(TAG, "setProgressState:$state") 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) onboardingService.initData(targetSubId, context,callback)
} }
@@ -445,10 +512,20 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
var onboardingService:SimOnboardingService = SimOnboardingService() var onboardingService:SimOnboardingService = SimOnboardingService()
const val TAG = "SimOnboardingActivity" const val TAG = "SimOnboardingActivity"
const val SUB_ID = "sub_id" const val SUB_ID = "sub_id"
const val CALLBACK_ERROR = -1
const val CALLBACK_ONBOARDING_COMPLETE = 1 enum class ErrorType(val value:Int){
const val CALLBACK_SETUP_NAME = 2 ERROR_NONE(-1),
const val CALLBACK_SETUP_PRIMARY_SIM = 3 ERROR_EUICC_SLOT(1),
const val CALLBACK_FINISH = 4 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.UiccCardInfo
import android.telephony.UiccSlotInfo import android.telephony.UiccSlotInfo
import android.util.Log 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.setAutomaticData
import com.android.settings.spa.network.setDefaultData import com.android.settings.spa.network.setDefaultData
import com.android.settings.spa.network.setDefaultSms import com.android.settings.spa.network.setDefaultSms
@@ -31,7 +32,6 @@ import com.android.settingslib.utils.ThreadUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
private const val TAG = "SimOnboardingService" private const val TAG = "SimOnboardingService"
private const val INVALID = SubscriptionManager.INVALID_SUBSCRIPTION_ID private const val INVALID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -60,7 +60,7 @@ class SimOnboardingService {
.map { it.subscriptionId } .map { it.subscriptionId }
.firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID .firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
} }
var callback: (Int) -> Unit = {} var callback: (CallbackType) -> Unit = {}
var isMultipleEnabledProfilesSupported: Boolean = false var isMultipleEnabledProfilesSupported: Boolean = false
get() { get() {
@@ -135,24 +135,24 @@ class SimOnboardingService {
userSelectedSubInfoList.clear() userSelectedSubInfoList.clear()
} }
fun initData(inputTargetSubId:Int,context: Context, callback: (Int) -> Unit) { fun initData(inputTargetSubId: Int,
context: Context,
callback: (CallbackType) -> Unit) {
this.callback = callback this.callback = callback
targetSubId = inputTargetSubId targetSubId = inputTargetSubId
subscriptionManager = context.getSystemService(SubscriptionManager::class.java) subscriptionManager = context.getSystemService(SubscriptionManager::class.java)
telephonyManager = context.getSystemService(TelephonyManager::class.java) telephonyManager = context.getSystemService(TelephonyManager::class.java)
Log.d( Log.d(
TAG, "startInit: targetSubId:$targetSubId" TAG, "startInit: targetSubId:$targetSubId, activeSubInfoList: $activeSubInfoList"
) )
activeSubInfoList = SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
ThreadUtils.postOnBackgroundThread { ThreadUtils.postOnBackgroundThread {
activeSubInfoList = SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
availableSubInfoList = SubscriptionUtil.getAvailableSubscriptions(context) availableSubInfoList = SubscriptionUtil.getAvailableSubscriptions(context)
targetSubInfo = targetSubInfo =
availableSubInfoList.find { subInfo -> subInfo.subscriptionId == targetSubId } availableSubInfoList.find { subInfo -> subInfo.subscriptionId == targetSubId }
targetSubInfo?.let { userSelectedSubInfoList.add(it) } targetSubInfo?.let { userSelectedSubInfoList.add(it) }
Log.d( Log.d(TAG, "targetSubId: $targetSubId , targetSubInfo: $targetSubInfo")
TAG, "targetSubId: $targetSubId" + ", targetSubInfo: $targetSubInfo" +
". activeSubInfoList: $activeSubInfoList"
)
slotInfoList = telephonyManager?.uiccSlotsInfo?.toList() ?: listOf() slotInfoList = telephonyManager?.uiccSlotsInfo?.toList() ?: listOf()
Log.d(TAG, "slotInfoList: $slotInfoList.") Log.d(TAG, "slotInfoList: $slotInfoList.")
uiccCardInfoList = telephonyManager?.uiccCardsInfo!! uiccCardInfoList = telephonyManager?.uiccCardsInfo!!
@@ -196,6 +196,16 @@ class SimOnboardingService {
return userSelectedSubInfoList.toList() 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) { fun addItemForRenaming(subInfo: SubscriptionInfo, newName: String) {
if (subInfo.displayName == newName) { if (subInfo.displayName == newName) {
return return
@@ -211,8 +221,12 @@ class SimOnboardingService {
return renameMutableMap[subInfo.subscriptionId] ?: subInfo.displayName.toString() return renameMutableMap[subInfo.subscriptionId] ?: subInfo.displayName.toString()
} }
fun addCurrentItemForSelectedSim(){ fun addCurrentItemForSelectedSim() {
userSelectedSubInfoList.addAll(activeSubInfoList) if (userSelectedSubInfoList.size < getActiveModemCount) {
userSelectedSubInfoList.addAll(activeSubInfoList)
Log.d(TAG, "addCurrentItemForSelectedSim: userSelectedSubInfoList:" +
", $userSelectedSubInfoList")
}
} }
fun addItemForSelectedSim(selectedSubInfo: SubscriptionInfo) { fun addItemForSelectedSim(selectedSubInfo: SubscriptionInfo) {
@@ -249,7 +263,7 @@ class SimOnboardingService {
fun startActivatingSim(){ fun startActivatingSim(){
// TODO: start to activate sim // TODO: start to activate sim
callback(SimOnboardingActivity.CALLBACK_FINISH) callback(CallbackType.CALLBACK_FINISH)
} }
suspend fun startSetupName() { suspend fun startSetupName() {
@@ -261,7 +275,7 @@ class SimOnboardingService {
) )
} }
// next action is SETUP_PRIMARY_SIM // 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 // 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.SelectableSubscriptions;
import com.android.settings.network.helper.SubscriptionAnnotation; import com.android.settings.network.helper.SubscriptionAnnotation;
import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity; import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity; import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -557,13 +559,21 @@ public class SubscriptionUtil {
* @param context {@code Context} * @param context {@code Context}
* @param subId The id of subscription need to be deleted. * @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)) { if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
Log.i(TAG, "Unable to delete subscription due to invalid subscription ID."); Log.i(TAG, "Unable to delete subscription due to invalid subscription ID.");
return; return;
} }
// TODO(b/325693582): Add verification if carrier is RAC and logic for new dialog final int[] carriersThatUseRAC = context.getResources().getIntArray(
context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId)); 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));
}
} }
/** /**

View File

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

View File

@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.android.internal.telephony.flags.Flags;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.network.CarrierConfigCache; import com.android.settings.network.CarrierConfigCache;
@@ -58,6 +59,11 @@ public class SatelliteSettingPreferenceController extends
@Override @Override
public int getAvailabilityStatus(int subId) { public int getAvailabilityStatus(int subId) {
if (!Flags.carrierEnabledSatelliteFlag()) {
logd("getAvailabilityStatus() : carrierEnabledSatelliteFlag is disabled");
return UNSUPPORTED_ON_DEVICE;
}
final PersistableBundle carrierConfig = mCarrierConfigCache.getConfigForSubId(subId); final PersistableBundle carrierConfig = mCarrierConfigCache.getConfigForSubId(subId);
final boolean isSatelliteAttachSupported = carrierConfig.getBoolean( final boolean isSatelliteAttachSupported = carrierConfig.getBoolean(
CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL); 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.CustomSliceRegistry;
import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.media.BluetoothMediaDevice; import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputConstants; import com.android.settingslib.media.MediaOutputConstants;
@@ -94,7 +95,9 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
@VisibleForTesting @VisibleForTesting
boolean isSupportEndItem() { boolean isSupportEndItem() {
return getWorker() != null && getWorker().isBroadcastSupported() return Flags.legacyLeAudioSharing()
&& getWorker() != null
&& getWorker().isBroadcastSupported()
&& (getWorker().isDeviceBroadcasting() || isConnectedBLEDevice()); && (getWorker().isDeviceBroadcasting() || isConnectedBLEDevice());
} }
@@ -114,8 +117,9 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
if (mPreference != null) { if (mPreference != null) {
if (mPreference.isMuted()) { if (mPreference.isMuted()) {
mPreference.updateContentDescription( mPreference.updateContentDescription(
mContext.getString(R.string.volume_content_description_silent_mode, mContext.getString(
mPreference.getTitle())); R.string.volume_content_description_silent_mode,
mPreference.getTitle()));
} else { } else {
mPreference.updateContentDescription(mPreference.getTitle()); mPreference.updateContentDescription(mPreference.getTitle());
} }
@@ -134,11 +138,16 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
if (getWorker().isDeviceBroadcasting()) { if (getWorker().isDeviceBroadcasting()) {
intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME); intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME);
intent.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG); intent.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, intent.putExtra(
MediaOutputConstants.EXTRA_PACKAGE_NAME,
getWorker().getActiveLocalMediaController().getPackageName()); getWorker().getActiveLocalMediaController().getPackageName());
pi = PendingIntent.getBroadcast(context, 0 /* requestCode */, intent, pi =
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); PendingIntent.getBroadcast(
context,
0 /* requestCode */,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else { } else {
final CachedBluetoothDevice bluetoothDevice = final CachedBluetoothDevice bluetoothDevice =
((BluetoothMediaDevice) mMediaDevice).getCachedDevice(); ((BluetoothMediaDevice) mMediaDevice).getCachedDevice();
@@ -147,15 +156,21 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
return null; return null;
} }
intent.setAction(ACTION_LAUNCH_BROADCAST_DIALOG); intent.setAction(ACTION_LAUNCH_BROADCAST_DIALOG);
intent.putExtra(BluetoothBroadcastDialog.KEY_APP_LABEL, intent.putExtra(
BluetoothBroadcastDialog.KEY_APP_LABEL,
Utils.getApplicationLabel(mContext, getWorker().getPackageName())); Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
intent.putExtra(BluetoothBroadcastDialog.KEY_DEVICE_ADDRESS, intent.putExtra(
bluetoothDevice.getAddress()); BluetoothBroadcastDialog.KEY_DEVICE_ADDRESS, bluetoothDevice.getAddress());
intent.putExtra(BluetoothBroadcastDialog.KEY_MEDIA_STREAMING, getWorker() != null intent.putExtra(
&& getWorker().getActiveLocalMediaController() != null); BluetoothBroadcastDialog.KEY_MEDIA_STREAMING,
getWorker() != null && getWorker().getActiveLocalMediaController() != null);
pi = PendingIntent.getActivity(context, 0 /* requestCode */, intent, pi =
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); PendingIntent.getActivity(
context,
0 /* requestCode */,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} }
final IconCompat icon = getBroadcastIcon(context); final IconCompat icon = getBroadcastIcon(context);
@@ -164,8 +179,8 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
} }
private IconCompat getBroadcastIcon(Context context) { private IconCompat getBroadcastIcon(Context context) {
final Drawable drawable = context.getDrawable( final Drawable drawable =
com.android.settingslib.R.drawable.settings_input_antenna); context.getDrawable(com.android.settingslib.R.drawable.settings_input_antenna);
if (drawable != null) { if (drawable != null) {
drawable.setTint(Utils.getColorAccentDefaultColor(context)); drawable.setTint(Utils.getColorAccentDefaultColor(context));
return Utils.createIconWithDrawable(drawable); return Utils.createIconWithDrawable(drawable);

View File

@@ -136,9 +136,7 @@ public class SimDialogActivity extends FragmentActivity {
} }
if (Flags.isDualSimOnboardingEnabled() if (Flags.isDualSimOnboardingEnabled()
&& getProgressState() == SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING && (dialogType == DATA_PICK
&& (dialogType == PREFERRED_PICK
|| dialogType == DATA_PICK
|| dialogType == CALLS_PICK || dialogType == CALLS_PICK
|| dialogType == SMS_PICK)) { || dialogType == SMS_PICK)) {
Log.d(TAG, "Finish the sim dialog since the sim onboarding is shown"); 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 android.util.Log
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType import androidx.navigation.NavType
@@ -33,6 +34,7 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.SimOnboardingActivity import com.android.settings.network.SimOnboardingActivity
import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
import com.android.settings.network.SimOnboardingService import com.android.settings.network.SimOnboardingService
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -92,7 +94,7 @@ fun PageImpl(onboardingService:SimOnboardingService,navHostController: NavHostCo
val context = LocalContext.current val context = LocalContext.current
var finishOnboarding: () -> Unit = { var finishOnboarding: () -> Unit = {
context.getActivity()?.finish() context.getActivity()?.finish()
onboardingService.callback(SimOnboardingActivity.CALLBACK_FINISH) onboardingService.callback(CallbackType.CALLBACK_FINISH)
} }
NavHost( NavHost(
@@ -101,10 +103,13 @@ fun PageImpl(onboardingService:SimOnboardingService,navHostController: NavHostCo
) { ) {
composable(route = SimOnboardingScreen.LabelSim.name) { composable(route = SimOnboardingScreen.LabelSim.name) {
val nextPage = val nextPage =
if (onboardingService.isMultipleEnabledProfilesSupported && onboardingService.isAllOfSlotAssigned) { if (onboardingService.isMultipleEnabledProfilesSupported
&& onboardingService.isAllOfSlotAssigned) {
SimOnboardingScreen.SelectSim.name SimOnboardingScreen.SelectSim.name
} else { } else {
onboardingService.addCurrentItemForSelectedSim() LaunchedEffect(Unit) {
onboardingService.addCurrentItemForSelectedSim()
}
SimOnboardingScreen.PrimarySim.name SimOnboardingScreen.PrimarySim.name
} }
SimOnboardingLabelSimImpl( SimOnboardingLabelSimImpl(
@@ -116,7 +121,7 @@ fun PageImpl(onboardingService:SimOnboardingService,navHostController: NavHostCo
composable(route = SimOnboardingScreen.PrimarySim.name) { composable(route = SimOnboardingScreen.PrimarySim.name) {
SimOnboardingPrimarySimImpl( SimOnboardingPrimarySimImpl(
nextAction = { nextAction = {
onboardingService.callback(SimOnboardingActivity.CALLBACK_ONBOARDING_COMPLETE) onboardingService.callback(CallbackType.CALLBACK_ONBOARDING_COMPLETE)
context.getActivity()?.finish() context.getActivity()?.finish()
}, },
cancelAction = finishOnboarding, cancelAction = finishOnboarding,

View File

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

View File

@@ -18,24 +18,33 @@ package com.android.settings.wifi
import android.content.Context import android.content.Context
import android.net.wifi.WifiManager 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.Composable
import androidx.compose.runtime.getValue 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.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.fragment.app.Fragment
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.settings.R import com.android.settings.R
import com.android.settings.spa.preference.ComposePreferenceController import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.framework.compose.OverridableFlow 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.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.wifi.flags.Flags import com.android.wifi.flags.Flags
import com.android.wifitrackerlib.WifiEntry
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow 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) : class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
ComposePreferenceController(context, preferenceKey) { ComposePreferenceController(context, preferenceKey) {
@@ -47,6 +56,8 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
@Composable @Composable
override fun Content() { override fun Content() {
val checked by wepAllowedFlow.flow.collectAsStateWithLifecycle(initialValue = null) val checked by wepAllowedFlow.flow.collectAsStateWithLifecycle(initialValue = null)
var openDialog by rememberSaveable { mutableStateOf(false) }
val wifiInfo = wifiManager.connectionInfo
SwitchPreference(object : SwitchPreferenceModel { SwitchPreference(object : SwitchPreferenceModel {
override val title = stringResource(R.string.wifi_allow_wep_networks) override val title = stringResource(R.string.wifi_allow_wep_networks)
override val summary = { getSummary() } override val summary = { getSummary() }
@@ -54,10 +65,40 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
override val changeable: () -> Boolean override val changeable: () -> Boolean
get() = { carrierAllowed } get() = { carrierAllowed }
override val onCheckedChange: (Boolean) -> Unit = { newChecked -> override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
wifiManager.setWepAllowed(newChecked) if (!newChecked && wifiInfo.currentSecurityType == WifiEntry.SECURITY_WEP) {
wepAllowedFlow.override(newChecked) 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( 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 static org.mockito.Mockito.when;
import android.app.UiModeManager; import android.app.UiModeManager;
import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@@ -59,7 +60,8 @@ public class ColorContrastFragmentTest {
@Test @Test
public void getMetricsCategory_returnsCorrectCategory() { public void getMetricsCategory_returnsCorrectCategory() {
assertThat(mFragment.getMetricsCategory()).isEqualTo(0); assertThat(mFragment.getMetricsCategory()).isEqualTo(
SettingsEnums.ACCESSIBILITY_COLOR_CONTRAST);
} }
@Test @Test

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@ android_test {
"androidx.test.rules", "androidx.test.rules",
"androidx.test.ext.junit", "androidx.test.ext.junit",
"androidx.preference_preference", "androidx.preference_preference",
"flag-junit",
"mockito-target-minus-junit4", "mockito-target-minus-junit4",
"platform-test-annotations", "platform-test-annotations",
"platform-test-rules", "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
}
}
}