Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/res/layout/keyboard_layout_picker_one_pane_land.xml b/res/layout/keyboard_layout_picker_one_pane_land.xml
new file mode 100644
index 0000000..fb5554f
--- /dev/null
+++ b/res/layout/keyboard_layout_picker_one_pane_land.xml
@@ -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>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index afd6fdd..9e91dcc 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -808,4 +808,8 @@
<!-- Array of carrier id to allow the pSIM conversion-->
<integer-array name="config_psim_conversion_menu_enabled_carrier" translatable="false">
</integer-array>
+
+ <!-- Array of carrier id that uses reusable activation code-->
+ <integer-array name="config_carrier_use_rac" translatable="false">
+ </integer-array>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index bdf329c..cbfd3a8 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -168,6 +168,8 @@
<!-- Keyboard -->
<dimen name="keyboard_picker_margin_large">68dp</dimen>
<dimen name="keyboard_picker_margin">24dp</dimen>
+ <dimen name="keyboard_picker_margin_one_pane_large">48dp</dimen>
+ <dimen name="keyboard_picker_margin_one_pane">24dp</dimen>
<dimen name="keyboard_picker_margin_small">16dp</dimen>
<dimen name="keyboard_picker_radius">28dp</dimen>
<dimen name="keyboard_picker_text_size">16sp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cf13dfb..a369d3d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5164,8 +5164,8 @@
<string name="accessibility_hearingaid_instruction_continue_button">Continue</string>
<!-- Title for the accessibility preference for hearing devices. [CHAR LIMIT=35] -->
<string name="accessibility_hearingaid_title">Hearing devices</string>
- <!-- Introduction for the Hearing devices page to introduce feature. [CHAR LIMIT=NONE] -->
- <string name="accessibility_hearingaid_intro">You can use hearing aids, cochlear implants, and other amplification devices with your phone</string>
+ <!-- Introduction for the Hearing devices page to introduce feature. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=5856992709195963850] -->
+ <string name="accessibility_hearingaid_intro">Set up and manage ASHA and LE Audio hearing aids, cochlear implants, and other amplification devices</string>
<!-- Summary for the accessibility preference for hearing aid when not connected. [CHAR LIMIT=50] -->
<string name="accessibility_hearingaid_not_connected_summary">No hearing devices connected</string>
<!-- Summary for the accessibility preference for hearing aid when adding new devices. [CHAR LIMIT=50] -->
@@ -5200,6 +5200,8 @@
<string name="accessibility_hac_mode_summary">Improves compatibility with telecoils and reduces unwanted noise</string>
<!-- Title for accessibility hearing device page footer. [CHAR LIMIT=40] -->
<string name="accessibility_hearing_device_about_title">About hearing devices</string>
+ <!-- Description for text in accessibility hearing aids footer. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=7451899224828040581] -->
+ <string name="accessibility_hearing_device_footer_summary">To find other hearing devices that aren’t supported by ASHA or LE Audio, tap <b>Pair new device</b> > <b>See more devices</b></string>
<!-- Title for the pair hearing device page. [CHAR LIMIT=25] -->
<string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string>
<!-- Subtitle for the pair hearing device page. [CHAR LIMIT=NONE] -->
@@ -12342,6 +12344,15 @@
<!-- Summary for UWB preference when UWB is unavailable due to regulatory requirements. [CHAR_LIMIT=NONE]-->
<string name="uwb_settings_summary_no_uwb_regulatory">UWB is unavailable in the current location</string>
+ <!-- Title for Thread network preference [CHAR_LIMIT=60] -->
+ <string name="thread_network_settings_title">Thread</string>
+
+ <!-- Summary for Thread network preference. [CHAR_LIMIT=NONE]-->
+ <string name="thread_network_settings_summary">Connect to compatible devices using Thread for a seamless smart home experience</string>
+
+ <!-- Summary for Thread network preference when airplane mode is enabled. [CHAR_LIMIT=NONE]-->
+ <string name="thread_network_settings_summary_airplane_mode">Turn off airplane mode to use Thread</string>
+
<!-- Label for the camera use toggle [CHAR LIMIT=40] -->
<string name="camera_toggle_title">Camera access</string>
<!-- Label for the camera use toggle [CHAR LIMIT=40] -->
diff --git a/res/xml/accessibility_hearing_aids.xml b/res/xml/accessibility_hearing_aids.xml
index 27a4212..57a0fe2 100644
--- a/res/xml/accessibility_hearing_aids.xml
+++ b/res/xml/accessibility_hearing_aids.xml
@@ -60,4 +60,11 @@
settings:searchable="true"
settings:controller="com.android.settings.accessibility.HearingAidCompatibilityPreferenceController"/>
</PreferenceCategory>
+
+ <com.android.settings.accessibility.AccessibilityFooterPreference
+ android:key="hearing_device_footer"
+ android:title="@string/accessibility_hearing_device_footer_summary"
+ android:selectable="false"
+ settings:searchable="false"
+ settings:controller="com.android.settings.accessibility.HearingDeviceFooterPreferenceController"/>
</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml
index 66cd46b..87db619 100644
--- a/res/xml/connected_devices_advanced.xml
+++ b/res/xml/connected_devices_advanced.xml
@@ -63,6 +63,15 @@
settings:useAdminDisabledSummary="true"
settings:userRestriction="no_ultra_wideband_radio" />
+ <com.android.settingslib.RestrictedSwitchPreference
+ android:key="thread_network_settings"
+ android:title="@string/thread_network_settings_title"
+ android:order="110"
+ android:summary="@string/summary_placeholder"
+ settings:controller="com.android.settings.connecteddevice.threadnetwork.ThreadNetworkPreferenceController"
+ settings:userRestriction="no_thread_network"
+ settings:useAdminDisabledSummary="true"/>
+
<PreferenceCategory
android:key="dashboard_tile_placeholder"
android:order="-8" />
diff --git a/res/xml/hearing_device_pairing_fragment.xml b/res/xml/hearing_device_pairing_fragment.xml
index ce7f39f..d84f22b 100644
--- a/res/xml/hearing_device_pairing_fragment.xml
+++ b/res/xml/hearing_device_pairing_fragment.xml
@@ -38,5 +38,4 @@
settings:useAdminDisabledSummary="true"
settings:controller="com.android.settings.accessibility.ViewAllBluetoothDevicesPreferenceController"/>
</PreferenceCategory>
-
</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 27a87f3..02205c1 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -38,6 +38,7 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.flags.Flags;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -56,6 +57,7 @@
import com.android.settings.Settings.WifiSettingsActivity;
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
import com.android.settings.applications.manageapplications.ManageApplications;
+import com.android.settings.connecteddevice.NfcAndPaymentFragment;
import com.android.settings.core.OnActivityResultListener;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.core.SubSettingLauncher;
@@ -63,6 +65,7 @@
import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.homepage.SettingsHomepageActivity;
import com.android.settings.homepage.TopLevelSettings;
+import com.android.settings.nfc.PaymentSettings;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.PasswordUtils;
import com.android.settings.wfd.WifiDisplaySettings;
@@ -828,12 +831,29 @@
if (ai == null || ai.metaData == null) return;
mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
mHighlightMenuKey = ai.metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY);
+ /* TODO(b/327036144) Once the Flags.walletRoleEnabled() is rolled out, we will replace
+ value for the fragment class within the com.android.settings.nfc.PaymentSettings
+ activity with com.android.settings.connecteddevice.NfcAndPaymentFragment so that this
+ code can be removed.
+ */
+ if (shouldOverrideContactlessPaymentRouting()) {
+ overrideContactlessPaymentRouting();
+ }
} catch (NameNotFoundException nnfe) {
// No recovery
Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
}
}
+ private boolean shouldOverrideContactlessPaymentRouting() {
+ return Flags.walletRoleEnabled()
+ && TextUtils.equals(PaymentSettings.class.getName(), mFragmentClass);
+ }
+
+ private void overrideContactlessPaymentRouting() {
+ mFragmentClass = NfcAndPaymentFragment.class.getName();
+ }
+
// give subclasses access to the Next button
public boolean hasNextButton() {
return mNextButton != null;
diff --git a/src/com/android/settings/accessibility/ColorContrastFragment.java b/src/com/android/settings/accessibility/ColorContrastFragment.java
index a572657..904826e 100644
--- a/src/com/android/settings/accessibility/ColorContrastFragment.java
+++ b/src/com/android/settings/accessibility/ColorContrastFragment.java
@@ -16,6 +16,8 @@
package com.android.settings.accessibility;
+import android.app.settings.SettingsEnums;
+
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -40,8 +42,7 @@
@Override
public int getMetricsCategory() {
- // TODO(b/326539398): Add metrics tracking for color contrast.
- return 0;
+ return SettingsEnums.ACCESSIBILITY_COLOR_CONTRAST;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
diff --git a/src/com/android/settings/accessibility/OWNERS b/src/com/android/settings/accessibility/OWNERS
index 1091a04..3bd156b 100644
--- a/src/com/android/settings/accessibility/OWNERS
+++ b/src/com/android/settings/accessibility/OWNERS
@@ -1,7 +1,12 @@
# Default reviewers for this and subdirectories.
+chunkulin@google.com
danielnorman@google.com
-menghanli@google.com
+
+# For hearing devices.
thomasli@google.com
+# Legacy owner(s).
+menghanli@google.com #{LAST_RESORT_SUGGESTION}
+
per-file HapticFeedbackIntensityPreferenceController.java = michaelwr@google.com
per-file *Vibration* = michaelwr@google.com
diff --git a/src/com/android/settings/connecteddevice/threadnetwork/OWNERS b/src/com/android/settings/connecteddevice/threadnetwork/OWNERS
new file mode 100644
index 0000000..4a35359
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/threadnetwork/OWNERS
@@ -0,0 +1 @@
+include platform/packages/modules/Connectivity:/thread/OWNERS
diff --git a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt
new file mode 100644
index 0000000..10e3f84
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt
@@ -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
+ }
+ }
+}
diff --git a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerFragment.java b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerFragment.java
index 85ba5fb..674b5bf 100644
--- a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerFragment.java
+++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerFragment.java
@@ -16,9 +16,11 @@
package com.android.settings.inputmethod;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.hardware.input.InputManager;
import android.hardware.input.KeyboardLayout;
@@ -35,6 +37,7 @@
import com.android.hardware.input.Flags;
import com.android.settings.R;
+import com.android.settings.activityembedding.ActivityEmbeddingUtils;
//TODO: b/316243168 - [Physical Keyboard Setting] Refactor NewKeyboardLayoutPickerFragment
public class NewKeyboardLayoutPickerFragment extends Fragment {
@@ -81,7 +84,7 @@
Bundle savedInstanceState) {
mInputManager = requireContext().getSystemService(InputManager.class);
ViewGroup fragmentView = (ViewGroup) inflater.inflate(
- R.layout.keyboard_layout_picker, container, false);
+ getPickerLayout(getResources().getConfiguration()), container, false);
mKeyboardLayoutPreview = fragmentView.findViewById(R.id.keyboard_layout_preview);
mKeyboardLayoutPreviewText = fragmentView.findViewById(R.id.keyboard_layout_preview_name);
if (!Flags.keyboardLayoutPreviewFlag()) {
@@ -102,6 +105,12 @@
return fragmentView;
}
+ private int getPickerLayout(Configuration configuration) {
+ return !ActivityEmbeddingUtils.isAlreadyEmbedded(this.getActivity())
+ && configuration.orientation == ORIENTATION_LANDSCAPE
+ ? R.layout.keyboard_layout_picker_one_pane_land : R.layout.keyboard_layout_picker;
+ }
+
private void updateViewMarginForPreviewFlagOff(ViewGroup fragmentView) {
LinearLayout previewContainer = fragmentView.findViewById(
R.id.keyboard_layout_picker_container);
diff --git a/src/com/android/settings/network/SimOnboardingActivity.kt b/src/com/android/settings/network/SimOnboardingActivity.kt
index 6347d7b..98bb5d7 100644
--- a/src/com/android/settings/network/SimOnboardingActivity.kt
+++ b/src/com/android/settings/network/SimOnboardingActivity.kt
@@ -45,6 +45,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -63,7 +64,10 @@
import com.android.settingslib.spa.SpaBaseDialogActivity
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.getDialogWidth
+import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
+import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
import com.android.settingslib.spa.widget.ui.SettingsTitle
import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.CoroutineScope
@@ -77,7 +81,7 @@
class SimOnboardingActivity : SpaBaseDialogActivity() {
lateinit var scope: CoroutineScope
lateinit var showBottomSheet: MutableState<Boolean>
- lateinit var showError: MutableState<Boolean>
+ lateinit var showError: MutableState<ErrorType>
lateinit var showProgressDialog: MutableState<Boolean>
private var switchToEuiccSubscriptionSidecar: SwitchToEuiccSubscriptionSidecar? = null
@@ -102,7 +106,7 @@
if (onboardingService.activeSubInfoList.isEmpty()) {
// TODO: refactor and replace the ToggleSubscriptionDialogActivity
- Log.e(TAG, "onboardingService.activeSubInfoList is empty" +
+ Log.d(TAG, "onboardingService.activeSubInfoList is empty" +
", start ToggleSubscriptionDialogActivity")
this.startActivity(ToggleSubscriptionDialogActivity
.getIntent(this.applicationContext, targetSubId, true))
@@ -113,10 +117,6 @@
switchToEuiccSubscriptionSidecar = SwitchToEuiccSubscriptionSidecar.get(fragmentManager)
switchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(fragmentManager)
enableMultiSimSidecar = EnableMultiSimSidecar.get(fragmentManager)
-
- setContent {
- Content()
- }
}
override fun finish() {
@@ -125,15 +125,14 @@
super.finish()
}
- var callbackListener: (Int) -> Unit = {
+ var callbackListener: (CallbackType) -> Unit = {
Log.d(TAG, "Receive the CALLBACK: $it")
when (it) {
- CALLBACK_ERROR -> {
+ CallbackType.CALLBACK_ERROR -> {
setProgressDialog(false)
- showError.value = true
}
- CALLBACK_ONBOARDING_COMPLETE -> {
+ CallbackType.CALLBACK_ONBOARDING_COMPLETE -> {
showBottomSheet.value = false
setProgressDialog(true)
scope.launch {
@@ -143,19 +142,19 @@
}
}
- CALLBACK_SETUP_NAME -> {
+ CallbackType.CALLBACK_SETUP_NAME -> {
scope.launch {
onboardingService.startSetupName()
}
}
- CALLBACK_SETUP_PRIMARY_SIM -> {
+ CallbackType.CALLBACK_SETUP_PRIMARY_SIM -> {
scope.launch {
onboardingService.startSetupPrimarySim(this@SimOnboardingActivity)
}
}
- CALLBACK_FINISH -> {
+ CallbackType.CALLBACK_FINISH -> {
finish()
}
}
@@ -178,16 +177,14 @@
@Composable
override fun Content() {
showBottomSheet = remember { mutableStateOf(false) }
- showError = remember { mutableStateOf(false) }
+ showError = remember { mutableStateOf(ErrorType.ERROR_NONE) }
showProgressDialog = remember { mutableStateOf(false) }
scope = rememberCoroutineScope()
registerSidecarReceiverFlow()
- if(showError.value){
- // show error
- return
- }
+ ErrorDialogImpl()
+
LaunchedEffect(Unit) {
if (onboardingService.activeSubInfoList.isNotEmpty()) {
showBottomSheet.value = true
@@ -255,6 +252,56 @@
}
@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
fun registerSidecarReceiverFlow(){
switchToEuiccSubscriptionSidecar?.sidecarReceiverFlow()
?.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
@@ -321,13 +368,14 @@
SidecarFragment.State.SUCCESS -> {
Log.i(TAG, "Successfully enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset()
- callbackListener(CALLBACK_SETUP_NAME)
+ callbackListener(CallbackType.CALLBACK_SETUP_NAME)
}
SidecarFragment.State.ERROR -> {
Log.i(TAG, "Failed to enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset()
- callbackListener(CALLBACK_ERROR)
+ showError.value = ErrorType.ERROR_EUICC_SLOT
+ callbackListener(CallbackType.CALLBACK_ERROR)
// TODO: showErrorDialog and using privileged_action_disable_fail_title and
// privileged_action_disable_fail_text
}
@@ -340,13 +388,14 @@
Log.i(TAG, "Successfully switched to removable slot.")
switchToRemovableSlotSidecar!!.reset()
onboardingService.handleTogglePsimAction()
- callbackListener(CALLBACK_SETUP_NAME)
+ callbackListener(CallbackType.CALLBACK_SETUP_NAME)
}
SidecarFragment.State.ERROR -> {
Log.e(TAG, "Failed switching to removable slot.")
switchToRemovableSlotSidecar!!.reset()
- callbackListener(CALLBACK_ERROR)
+ showError.value = ErrorType.ERROR_REMOVABLE_SLOT
+ callbackListener(CallbackType.CALLBACK_ERROR)
// TODO: showErrorDialog and using sim_action_enable_sim_fail_title and
// sim_action_enable_sim_fail_text
}
@@ -364,7 +413,8 @@
SidecarFragment.State.ERROR -> {
enableMultiSimSidecar!!.reset()
Log.i(TAG, "Failed to switch to DSDS without rebooting.")
- callbackListener(CALLBACK_ERROR)
+ showError.value = ErrorType.ERROR_ENABLE_DSDS
+ callbackListener(CallbackType.CALLBACK_ERROR)
// TODO: showErrorDialog and using dsds_activation_failure_title and
// dsds_activation_failure_body_msg2
}
@@ -387,7 +437,7 @@
}
Log.i(TAG, "DSDS enabled, start to enable pSIM profile.")
onboardingService.handleTogglePsimAction()
- callbackListener(CALLBACK_FINISH)
+ callbackListener(CallbackType.CALLBACK_FINISH)
}
@Composable
@@ -443,7 +493,7 @@
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)
}
@@ -462,10 +512,20 @@
var onboardingService:SimOnboardingService = SimOnboardingService()
const val TAG = "SimOnboardingActivity"
const val SUB_ID = "sub_id"
- const val CALLBACK_ERROR = -1
- const val CALLBACK_ONBOARDING_COMPLETE = 1
- const val CALLBACK_SETUP_NAME = 2
- const val CALLBACK_SETUP_PRIMARY_SIM = 3
- const val CALLBACK_FINISH = 4
+
+ enum class ErrorType(val value:Int){
+ ERROR_NONE(-1),
+ ERROR_EUICC_SLOT(1),
+ ERROR_REMOVABLE_SLOT(2),
+ ERROR_ENABLE_DSDS(3)
+ }
+
+ enum class CallbackType(val value:Int){
+ CALLBACK_ERROR(-1),
+ CALLBACK_ONBOARDING_COMPLETE(1),
+ CALLBACK_SETUP_NAME(2),
+ CALLBACK_SETUP_PRIMARY_SIM(3),
+ CALLBACK_FINISH(4)
+ }
}
}
\ No newline at end of file
diff --git a/src/com/android/settings/network/SimOnboardingService.kt b/src/com/android/settings/network/SimOnboardingService.kt
index 5387ad4..962741f 100644
--- a/src/com/android/settings/network/SimOnboardingService.kt
+++ b/src/com/android/settings/network/SimOnboardingService.kt
@@ -23,6 +23,7 @@
import android.telephony.UiccCardInfo
import android.telephony.UiccSlotInfo
import android.util.Log
+import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
import com.android.settings.spa.network.setAutomaticData
import com.android.settings.spa.network.setDefaultData
import com.android.settings.spa.network.setDefaultSms
@@ -31,7 +32,6 @@
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-
private const val TAG = "SimOnboardingService"
private const val INVALID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -60,7 +60,7 @@
.map { it.subscriptionId }
.firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
- var callback: (Int) -> Unit = {}
+ var callback: (CallbackType) -> Unit = {}
var isMultipleEnabledProfilesSupported: Boolean = false
get() {
@@ -135,7 +135,9 @@
userSelectedSubInfoList.clear()
}
- fun initData(inputTargetSubId:Int,context: Context, callback: (Int) -> Unit) {
+ fun initData(inputTargetSubId: Int,
+ context: Context,
+ callback: (CallbackType) -> Unit) {
this.callback = callback
targetSubId = inputTargetSubId
subscriptionManager = context.getSystemService(SubscriptionManager::class.java)
@@ -261,7 +263,7 @@
fun startActivatingSim(){
// TODO: start to activate sim
- callback(SimOnboardingActivity.CALLBACK_FINISH)
+ callback(CallbackType.CALLBACK_FINISH)
}
suspend fun startSetupName() {
@@ -273,7 +275,7 @@
)
}
// next action is SETUP_PRIMARY_SIM
- callback(SimOnboardingActivity.CALLBACK_SETUP_PRIMARY_SIM)
+ callback(CallbackType.CALLBACK_SETUP_PRIMARY_SIM)
}
}
@@ -302,7 +304,7 @@
}
// no next action, send finish
- callback(SimOnboardingActivity.CALLBACK_FINISH)
+ callback(CallbackType.CALLBACK_FINISH)
}
}
}
\ No newline at end of file
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index 2498ec9..83f6c38 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -49,9 +49,11 @@
import com.android.settings.network.helper.SelectableSubscriptions;
import com.android.settings.network.helper.SubscriptionAnnotation;
import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
+import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -557,13 +559,21 @@
* @param context {@code Context}
* @param subId The id of subscription need to be deleted.
*/
- public static void startDeleteEuiccSubscriptionDialogActivity(Context context, int subId) {
+ public static void startDeleteEuiccSubscriptionDialogActivity(Context context, int subId,
+ int carrierId) {
if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
Log.i(TAG, "Unable to delete subscription due to invalid subscription ID.");
return;
}
- // TODO(b/325693582): Add verification if carrier is RAC and logic for new dialog
- context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
+ final int[] carriersThatUseRAC = context.getResources().getIntArray(
+ R.array.config_carrier_use_rac);
+ boolean isCarrierRac = Arrays.stream(carriersThatUseRAC).anyMatch(cid -> cid == carrierId);
+
+ if (isCarrierRac && !isConnectedToWifiOrDifferentSubId(context, subId)) {
+ context.startActivity(EuiccRacConnectivityDialogActivity.getIntent(context, subId));
+ } else {
+ context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
+ }
}
/**
diff --git a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
index 64f9730..b7ee41d 100644
--- a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
+++ b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
@@ -33,6 +33,7 @@
class DeleteSimProfilePreferenceController(context: Context, preferenceKey: String) :
BasePreferenceController(context, preferenceKey) {
private var subscriptionId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ private var carrierId: Int = TelephonyManager.UNKNOWN_CARRIER_ID
private var subscriptionInfo: SubscriptionInfo? = null
private lateinit var preference: Preference
@@ -40,6 +41,9 @@
this.subscriptionId = subscriptionId
subscriptionInfo = SubscriptionUtil.getAvailableSubscriptions(mContext)
.find { it.subscriptionId == subscriptionId && it.isEmbedded }
+ subscriptionInfo?.let {
+ carrierId = it.carrierId
+ }
}
override fun getAvailabilityStatus() = when (subscriptionInfo) {
@@ -67,7 +71,8 @@
}
private fun deleteSim() {
- SubscriptionUtil.startDeleteEuiccSubscriptionDialogActivity(mContext, subscriptionId)
+ SubscriptionUtil.startDeleteEuiccSubscriptionDialogActivity(mContext, subscriptionId,
+ carrierId)
// result handled in MobileNetworkSettings
}
}
diff --git a/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java b/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
index 94940b3..5e8a3c3 100644
--- a/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
+++ b/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
@@ -29,6 +29,7 @@
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import com.android.internal.telephony.flags.Flags;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.network.CarrierConfigCache;
@@ -58,6 +59,11 @@
@Override
public int getAvailabilityStatus(int subId) {
+ if (!Flags.carrierEnabledSatelliteFlag()) {
+ logd("getAvailabilityStatus() : carrierEnabledSatelliteFlag is disabled");
+ return UNSUPPORTED_ON_DEVICE;
+ }
+
final PersistableBundle carrierConfig = mCarrierConfigCache.getConfigForSubId(subId);
final boolean isSatelliteAttachSupported = carrierConfig.getBoolean(
CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL);
diff --git a/src/com/android/settings/notification/MediaVolumePreferenceController.java b/src/com/android/settings/notification/MediaVolumePreferenceController.java
index 79df55a..e70cf95 100644
--- a/src/com/android/settings/notification/MediaVolumePreferenceController.java
+++ b/src/com/android/settings/notification/MediaVolumePreferenceController.java
@@ -37,6 +37,7 @@
import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputConstants;
@@ -94,7 +95,9 @@
@VisibleForTesting
boolean isSupportEndItem() {
- return getWorker() != null && getWorker().isBroadcastSupported()
+ return Flags.legacyLeAudioSharing()
+ && getWorker() != null
+ && getWorker().isBroadcastSupported()
&& (getWorker().isDeviceBroadcasting() || isConnectedBLEDevice());
}
@@ -114,8 +117,9 @@
if (mPreference != null) {
if (mPreference.isMuted()) {
mPreference.updateContentDescription(
- mContext.getString(R.string.volume_content_description_silent_mode,
- mPreference.getTitle()));
+ mContext.getString(
+ R.string.volume_content_description_silent_mode,
+ mPreference.getTitle()));
} else {
mPreference.updateContentDescription(mPreference.getTitle());
}
@@ -134,11 +138,16 @@
if (getWorker().isDeviceBroadcasting()) {
intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME);
intent.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
- intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
+ intent.putExtra(
+ MediaOutputConstants.EXTRA_PACKAGE_NAME,
getWorker().getActiveLocalMediaController().getPackageName());
- pi = PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ pi =
+ PendingIntent.getBroadcast(
+ context,
+ 0 /* requestCode */,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
final CachedBluetoothDevice bluetoothDevice =
((BluetoothMediaDevice) mMediaDevice).getCachedDevice();
@@ -147,15 +156,21 @@
return null;
}
intent.setAction(ACTION_LAUNCH_BROADCAST_DIALOG);
- intent.putExtra(BluetoothBroadcastDialog.KEY_APP_LABEL,
+ intent.putExtra(
+ BluetoothBroadcastDialog.KEY_APP_LABEL,
Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
- intent.putExtra(BluetoothBroadcastDialog.KEY_DEVICE_ADDRESS,
- bluetoothDevice.getAddress());
- intent.putExtra(BluetoothBroadcastDialog.KEY_MEDIA_STREAMING, getWorker() != null
- && getWorker().getActiveLocalMediaController() != null);
+ intent.putExtra(
+ BluetoothBroadcastDialog.KEY_DEVICE_ADDRESS, bluetoothDevice.getAddress());
+ intent.putExtra(
+ BluetoothBroadcastDialog.KEY_MEDIA_STREAMING,
+ getWorker() != null && getWorker().getActiveLocalMediaController() != null);
- pi = PendingIntent.getActivity(context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ pi =
+ PendingIntent.getActivity(
+ context,
+ 0 /* requestCode */,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
final IconCompat icon = getBroadcastIcon(context);
@@ -164,8 +179,8 @@
}
private IconCompat getBroadcastIcon(Context context) {
- final Drawable drawable = context.getDrawable(
- com.android.settingslib.R.drawable.settings_input_antenna);
+ final Drawable drawable =
+ context.getDrawable(com.android.settingslib.R.drawable.settings_input_antenna);
if (drawable != null) {
drawable.setTint(Utils.getColorAccentDefaultColor(context));
return Utils.createIconWithDrawable(drawable);
diff --git a/src/com/android/settings/sim/SimDialogActivity.java b/src/com/android/settings/sim/SimDialogActivity.java
index 4544e73..8840994 100644
--- a/src/com/android/settings/sim/SimDialogActivity.java
+++ b/src/com/android/settings/sim/SimDialogActivity.java
@@ -136,9 +136,7 @@
}
if (Flags.isDualSimOnboardingEnabled()
- && getProgressState() == SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
- && (dialogType == PREFERRED_PICK
- || dialogType == DATA_PICK
+ && (dialogType == DATA_PICK
|| dialogType == CALLS_PICK
|| dialogType == SMS_PICK)) {
Log.d(TAG, "Finish the sim dialog since the sim onboarding is shown");
diff --git a/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt b/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt
index cc17f93..838154f 100644
--- a/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt
+++ b/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt
@@ -34,6 +34,7 @@
import androidx.navigation.navArgument
import com.android.settings.R
import com.android.settings.network.SimOnboardingActivity
+import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
import com.android.settings.network.SimOnboardingService
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -93,7 +94,7 @@
val context = LocalContext.current
var finishOnboarding: () -> Unit = {
context.getActivity()?.finish()
- onboardingService.callback(SimOnboardingActivity.CALLBACK_FINISH)
+ onboardingService.callback(CallbackType.CALLBACK_FINISH)
}
NavHost(
@@ -120,7 +121,7 @@
composable(route = SimOnboardingScreen.PrimarySim.name) {
SimOnboardingPrimarySimImpl(
nextAction = {
- onboardingService.callback(SimOnboardingActivity.CALLBACK_ONBOARDING_COMPLETE)
+ onboardingService.callback(CallbackType.CALLBACK_ONBOARDING_COMPLETE)
context.getActivity()?.finish()
},
cancelAction = finishOnboarding,
diff --git a/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java
index f903745..3077637 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java
@@ -22,6 +22,7 @@
import static org.mockito.Mockito.when;
import android.app.UiModeManager;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -59,7 +60,8 @@
@Test
public void getMetricsCategory_returnsCorrectCategory() {
- assertThat(mFragment.getMetricsCategory()).isEqualTo(0);
+ assertThat(mFragment.getMetricsCategory()).isEqualTo(
+ SettingsEnums.ACCESSIBILITY_COLOR_CONTRAST);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java
index ed93473..a25f472 100644
--- a/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java
@@ -31,17 +31,23 @@
import android.media.AudioManager;
import android.media.session.MediaController;
import android.net.Uri;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.slice.builders.SliceAction;
import com.android.settings.media.MediaOutputIndicatorWorker;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputConstants;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -60,16 +66,16 @@
"android.settings.MEDIA_BROADCAST_DIALOG";
private static MediaOutputIndicatorWorker sMediaOutputIndicatorWorker;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private MediaVolumePreferenceController mController;
private Context mContext;
- @Mock
- private MediaController mMediaController;
- @Mock
- private MediaDevice mDevice1;
- @Mock
- private MediaDevice mDevice2;
+ @Mock private MediaController mMediaController;
+ @Mock private MediaDevice mDevice1;
+ @Mock private MediaDevice mDevice2;
@Before
public void setUp() {
@@ -77,8 +83,8 @@
mContext = RuntimeEnvironment.application;
mController = new MediaVolumePreferenceController(mContext);
- sMediaOutputIndicatorWorker = spy(
- new MediaOutputIndicatorWorker(mContext, VOLUME_MEDIA_URI));
+ sMediaOutputIndicatorWorker =
+ spy(new MediaOutputIndicatorWorker(mContext, VOLUME_MEDIA_URI));
when(mDevice1.isBLEDevice()).thenReturn(true);
when(mDevice2.isBLEDevice()).thenReturn(false);
}
@@ -101,8 +107,8 @@
@Test
public void isSliceableCorrectKey_returnsTrue() {
- final MediaVolumePreferenceController controller = new MediaVolumePreferenceController(
- mContext);
+ final MediaVolumePreferenceController controller =
+ new MediaVolumePreferenceController(mContext);
assertThat(controller.isSliceable()).isTrue();
}
@@ -112,6 +118,17 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ public void isSupportEndItem_flagOff_returnsFalse() {
+ doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
+ doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
+ doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
+
+ assertThat(mController.isSupportEndItem()).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_withBleDevice_returnsTrue() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -121,6 +138,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_notSupportedBroadcast_returnsFalse() {
doReturn(false).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
@@ -129,6 +147,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_withNonBleDevice_returnsFalse() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -138,6 +157,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_deviceIsBroadcastingAndConnectedToNonBleDevice_returnsTrue() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -147,6 +167,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_deviceIsNotBroadcastingAndConnectedToNonBleDevice_returnsFalse() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -155,8 +176,20 @@
assertThat(mController.isSupportEndItem()).isFalse();
}
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ public void getSliceEndItem_flagOff_getsNullSliceAction() {
+ doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
+ doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
+ doReturn(mDevice2).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
+
+ final SliceAction sliceAction = mController.getSliceEndItem(mContext);
+
+ assertThat(sliceAction).isNull();
+ }
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getSliceEndItem_NotSupportEndItem_getsNullSliceAction() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -168,22 +201,26 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getSliceEndItem_deviceIsBroadcasting_getsBroadcastIntent() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
- doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
+ doReturn(mMediaController)
+ .when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
final SliceAction sliceAction = mController.getSliceEndItem(mContext);
final PendingIntent endItemPendingIntent = sliceAction.getAction();
- final PendingIntent expectedToggleIntent = getBroadcastIntent(
- MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
+ final PendingIntent expectedToggleIntent =
+ getBroadcastIntent(
+ MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
assertThat(endItemPendingIntent).isEqualTo(expectedToggleIntent);
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getSliceEndItem_deviceIsNotBroadcasting_getsActivityIntent() {
final MediaDevice device = mock(BluetoothMediaDevice.class);
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
@@ -192,7 +229,8 @@
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(device).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
- doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
+ doReturn(mMediaController)
+ .when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
final SliceAction sliceAction = mController.getSliceEndItem(mContext);
@@ -215,13 +253,19 @@
private PendingIntent getBroadcastIntent(String action) {
final Intent intent = new Intent(action);
intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME);
- return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent,
+ return PendingIntent.getBroadcast(
+ mContext,
+ 0 /* requestCode */,
+ intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
private PendingIntent getActivityIntent(String action) {
final Intent intent = new Intent(action);
- return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent,
+ return PendingIntent.getActivity(
+ mContext,
+ 0 /* requestCode */,
+ intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
}
diff --git a/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java b/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java
index d696342..4b05b91 100644
--- a/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java
+++ b/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java
@@ -34,7 +34,6 @@
import com.android.settings.testutils.shadow.ShadowUserManager;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
@@ -44,7 +43,6 @@
import java.util.ArrayList;
import java.util.List;
-@Ignore("b/315133235")
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowUserManager.class, ShadowDevicePolicyManager.class})
public class RestrictedButtonTest {
diff --git a/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java
index 35dc666..530517f 100644
--- a/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java
@@ -35,18 +35,19 @@
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceViewHolder;
+import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowSettingsMediaPlayer;
import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.androidx.fragment.FragmentController;
@@ -55,7 +56,8 @@
public class VideoPreferenceTest {
private static final int VIDEO_WIDTH = 100;
private static final int VIDEO_HEIGHT = 150;
-
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private VideoPreference.AnimationController mAnimationController;
@Mock
private ImageView fakePreview;
@@ -68,9 +70,7 @@
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mContext = RuntimeEnvironment.application;
+ mContext = ApplicationProvider.getApplicationContext();
mAnimationController = spy(
new MediaAnimationController(mContext, R.raw.sample_video));
mVideoPreference = new VideoPreference(mContext, null /* attrs */);
@@ -141,7 +141,6 @@
assertThat(mAnimationController.isPlaying()).isTrue();
}
- @Ignore("b/315133235")
@Test
@Config(qualifiers = "mcc999")
public void onViewVisible_createAnimationController() {
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 0f844d0..c8e886c 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -26,6 +26,7 @@
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.preference_preference",
+ "flag-junit",
"mockito-target-minus-junit4",
"platform-test-annotations",
"platform-test-rules",
diff --git a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS
new file mode 100644
index 0000000..4a35359
--- /dev/null
+++ b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS
@@ -0,0 +1 @@
+include platform/packages/modules/Connectivity:/thread/OWNERS
diff --git a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt
new file mode 100644
index 0000000..644095d
--- /dev/null
+++ b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt
@@ -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
+ }
+ }
+}