Merge "[Thread] add Thread toggle in settings" into main am: 522e193947
Original change: https://android-review.googlesource.com/c/platform/packages/apps/Settings/+/2909326 Change-Id: I8640f2cf4a90203972a5db703ebb3e027224c746 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -12004,6 +12004,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] -->
|
||||||
|
@@ -70,6 +70,15 @@
|
|||||||
settings:userRestriction="no_ultra_wideband_radio"
|
settings:userRestriction="no_ultra_wideband_radio"
|
||||||
settings:useAdminDisabledSummary="true"/>
|
settings:useAdminDisabledSummary="true"/>
|
||||||
|
|
||||||
|
<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"/>
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
include platform/packages/modules/Connectivity:/thread/OWNERS
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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",
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
include platform/packages/modules/Connectivity:/thread/OWNERS
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user