[Thread] update Thread settings screen
Per b/327098435 the new Thread settings design proposed (go/android-thread) is approved. As a summary, this commit adds a new "connected devices > connection preference -> Thread" list item and decidated config page for Thread. Also, we simplified the airplane mode to delegate it to the mainline code to handle it Bug: 327098435 Test: atest SettingsUnitTests Merged-In: Iffbb2471f5a28ec57d30a378f22642fe6ac0b9cc Change-Id: Iffbb2471f5a28ec57d30a378f22642fe6ac0b9cc
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.net.thread.ThreadNetworkController
|
||||
import android.net.thread.ThreadNetworkController.StateCallback
|
||||
import android.net.thread.ThreadNetworkException
|
||||
import android.os.OutcomeReceiver
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.Context
|
||||
import android.util.Log
|
||||
import androidx.preference.PreferenceScreen
|
||||
import com.android.settings.R
|
||||
import com.android.settings.core.BasePreferenceController
|
||||
import com.android.settingslib.HelpUtils
|
||||
import com.android.settingslib.widget.FooterPreference
|
||||
|
||||
/**
|
||||
* The footer preference controller for Thread settings in
|
||||
* "Connected devices > Connection preferences > Thread".
|
||||
*/
|
||||
class ThreadNetworkFooterController(
|
||||
context: Context,
|
||||
preferenceKey: String
|
||||
) : BasePreferenceController(context, preferenceKey) {
|
||||
override fun getAvailabilityStatus(): Int {
|
||||
// The thread_network_settings screen won't be displayed and it doesn't matter if this
|
||||
// controller always return AVAILABLE
|
||||
return AVAILABLE
|
||||
}
|
||||
|
||||
override fun displayPreference(screen: PreferenceScreen) {
|
||||
val footer: FooterPreference? = screen.findPreference(KEY_PREFERENCE_FOOTER)
|
||||
if (footer != null) {
|
||||
footer.setLearnMoreAction { _ -> openLocaleLearnMoreLink() }
|
||||
footer.setLearnMoreText(mContext.getString(R.string.thread_network_settings_learn_more))
|
||||
}
|
||||
}
|
||||
|
||||
private fun openLocaleLearnMoreLink() {
|
||||
val intent = HelpUtils.getHelpIntent(
|
||||
mContext,
|
||||
mContext.getString(R.string.thread_network_settings_learn_more_link),
|
||||
mContext::class.java.name
|
||||
)
|
||||
if (intent != null) {
|
||||
mContext.startActivity(intent)
|
||||
} else {
|
||||
Log.w(TAG, "HelpIntent is null")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ThreadNetworkSettings"
|
||||
private const val KEY_PREFERENCE_FOOTER = "thread_network_settings_footer"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.app.settings.SettingsEnums
|
||||
import com.android.settings.R
|
||||
import com.android.settings.dashboard.DashboardFragment
|
||||
import com.android.settings.search.BaseSearchIndexProvider
|
||||
import com.android.settingslib.search.SearchIndexable
|
||||
|
||||
/** The fragment for Thread settings in "Connected devices > Connection preferences > Thread". */
|
||||
@SearchIndexable(forTarget = SearchIndexable.ALL and SearchIndexable.ARC.inv())
|
||||
class ThreadNetworkFragment : DashboardFragment() {
|
||||
override fun getPreferenceScreenResId() = R.xml.thread_network_settings
|
||||
|
||||
override fun getLogTag() = "ThreadNetworkFragment"
|
||||
|
||||
override fun getMetricsCategory() = SettingsEnums.CONNECTED_DEVICE_PREFERENCES_THREAD
|
||||
|
||||
companion object {
|
||||
/** For Search. */
|
||||
@JvmField
|
||||
val SEARCH_INDEX_DATA_PROVIDER = BaseSearchIndexProvider(R.xml.thread_network_settings)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.Context
|
||||
import android.net.thread.ThreadNetworkController
|
||||
import android.net.thread.ThreadNetworkController.StateCallback
|
||||
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.settings.R
|
||||
import com.android.settings.core.BasePreferenceController
|
||||
import com.android.settings.flags.Flags
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
* The fragment controller for Thread settings in
|
||||
* "Connected devices > Connection preferences > Thread".
|
||||
*/
|
||||
class ThreadNetworkFragmentController @VisibleForTesting constructor(
|
||||
context: Context,
|
||||
preferenceKey: String,
|
||||
private val executor: Executor,
|
||||
private val threadController: BaseThreadNetworkController?
|
||||
) : BasePreferenceController(context, preferenceKey), LifecycleEventObserver {
|
||||
private val stateCallback: StateCallback
|
||||
private var threadEnabled = false
|
||||
private var preference: Preference? = null
|
||||
|
||||
constructor(context: Context, preferenceKey: String) : this(
|
||||
context,
|
||||
preferenceKey,
|
||||
ContextCompat.getMainExecutor(context),
|
||||
ThreadNetworkUtils.getThreadNetworkController(context)
|
||||
)
|
||||
|
||||
init {
|
||||
stateCallback = newStateCallback()
|
||||
}
|
||||
|
||||
override fun getAvailabilityStatus(): Int {
|
||||
return if (!Flags.threadSettingsEnabled()) {
|
||||
CONDITIONALLY_UNAVAILABLE
|
||||
} else if (threadController == null) {
|
||||
UNSUPPORTED_ON_DEVICE
|
||||
} else {
|
||||
AVAILABLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSummary(): CharSequence {
|
||||
return if (threadEnabled) {
|
||||
mContext.getText(R.string.switch_on_text)
|
||||
} else {
|
||||
mContext.getText(R.string.switch_off_text)
|
||||
}
|
||||
}
|
||||
|
||||
override fun displayPreference(screen: PreferenceScreen) {
|
||||
super.displayPreference(screen)
|
||||
preference = screen.findPreference(preferenceKey)
|
||||
}
|
||||
|
||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
if (threadController == null) {
|
||||
return
|
||||
}
|
||||
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_START ->
|
||||
threadController.registerStateCallback(executor, stateCallback)
|
||||
|
||||
Lifecycle.Event.ON_STOP ->
|
||||
threadController.unregisterStateCallback(stateCallback)
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun newStateCallback(): StateCallback {
|
||||
return object : StateCallback {
|
||||
override fun onThreadEnableStateChanged(enabledState: Int) {
|
||||
threadEnabled = enabledState == ThreadNetworkController.STATE_ENABLED
|
||||
preference?.let { preference -> refreshSummary(preference) }
|
||||
}
|
||||
|
||||
override fun onDeviceRoleChanged(role: Int) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
/*
|
||||
* 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.settings.R
|
||||
import com.android.settings.core.TogglePreferenceController
|
||||
import com.android.settings.flags.Flags
|
||||
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.threadSettingsEnabled()) {
|
||||
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(source: 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.Context
|
||||
import android.net.thread.ThreadNetworkController
|
||||
import android.net.thread.ThreadNetworkController.StateCallback
|
||||
import android.net.thread.ThreadNetworkException
|
||||
import android.os.OutcomeReceiver
|
||||
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.settings.R
|
||||
import com.android.settings.core.TogglePreferenceController
|
||||
import com.android.settings.flags.Flags
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
* Controller for the "Use Thread" toggle in "Connected devices > Connection preferences > Thread".
|
||||
*/
|
||||
class ThreadNetworkToggleController @VisibleForTesting constructor(
|
||||
context: Context,
|
||||
key: String,
|
||||
private val executor: Executor,
|
||||
private val threadController: BaseThreadNetworkController?
|
||||
) : TogglePreferenceController(context, key), LifecycleEventObserver {
|
||||
private val stateCallback: StateCallback
|
||||
private var threadEnabled = false
|
||||
private var preference: Preference? = null
|
||||
|
||||
constructor(context: Context, key: String) : this(
|
||||
context,
|
||||
key,
|
||||
ContextCompat.getMainExecutor(context),
|
||||
ThreadNetworkUtils.getThreadNetworkController(context)
|
||||
)
|
||||
|
||||
init {
|
||||
stateCallback = newStateCallback()
|
||||
}
|
||||
|
||||
val isThreadSupportedOnDevice: Boolean
|
||||
get() = threadController != null
|
||||
|
||||
private fun newStateCallback(): StateCallback {
|
||||
return object : StateCallback {
|
||||
override fun onThreadEnableStateChanged(enabledState: Int) {
|
||||
threadEnabled = enabledState == ThreadNetworkController.STATE_ENABLED
|
||||
preference?.let { preference -> updateState(preference) }
|
||||
}
|
||||
|
||||
override fun onDeviceRoleChanged(role: Int) {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAvailabilityStatus(): Int {
|
||||
return if (!Flags.threadSettingsEnabled()) {
|
||||
CONDITIONALLY_UNAVAILABLE
|
||||
} else if (!isThreadSupportedOnDevice) {
|
||||
UNSUPPORTED_ON_DEVICE
|
||||
} else {
|
||||
AVAILABLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun displayPreference(screen: PreferenceScreen) {
|
||||
super.displayPreference(screen)
|
||||
preference = screen.findPreference(preferenceKey)
|
||||
}
|
||||
|
||||
override fun isChecked(): Boolean {
|
||||
return threadEnabled
|
||||
}
|
||||
|
||||
override fun setChecked(isChecked: Boolean): Boolean {
|
||||
if (threadController == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Avoids dead loop of setChecked -> threadController.setEnabled() ->
|
||||
// StateCallback.onThreadEnableStateChanged -> updateState -> setChecked
|
||||
if (isChecked == isChecked()) {
|
||||
return true
|
||||
}
|
||||
|
||||
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(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
if (threadController == null) {
|
||||
return
|
||||
}
|
||||
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_START -> {
|
||||
threadController.registerStateCallback(executor, stateCallback)
|
||||
}
|
||||
|
||||
Lifecycle.Event.ON_STOP -> {
|
||||
threadController.unregisterStateCallback(stateCallback)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSliceHighlightMenuRes(): Int {
|
||||
return R.string.menu_key_connected_devices
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ThreadNetworkSettings"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.Context
|
||||
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 androidx.annotation.VisibleForTesting
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/** Common utilities for Thread settings classes. */
|
||||
object ThreadNetworkUtils {
|
||||
/**
|
||||
* Retrieves the [BaseThreadNetworkController] instance that is backed by the Android
|
||||
* [ThreadNetworkController].
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user