Adding share toggle to the network details page
Adds the share toggle and checks if there is a conflict when user attempts to change the toggle. In case of a conflict, an alert dialog in shown to the user. Bug: 409845756 Flag: com.android.settings.connectivity.wifi_multiuser Test: Manual testing Change-Id: Ia57b967ad933ebb19f27f1e50d6a69210b84ac4c
This commit is contained in:
@@ -2569,6 +2569,22 @@
|
||||
<string name="retry">Retry</string>
|
||||
<!-- Label for the check box to share a network with other users on the same device -->
|
||||
<string name="wifi_shared">Share with other device users</string>
|
||||
<!-- Label for the shared wifi message -->
|
||||
<string name="shared_message">shared</string>
|
||||
<!-- Label for the private wifi message -->
|
||||
<string name="private_message">private</string>
|
||||
<!-- Label for the conflict wifi dialog confirm button -->
|
||||
<string name="wifi_conflict_dialog_confirm">Confirm</string>
|
||||
<!-- Label for the conflict wifi dialog cancel button -->
|
||||
<string name="wifi_conflict_dialog_cancel">Cancel</string>
|
||||
<!-- Title for the preference to share a network with other users on the same device -->
|
||||
<string name="wifi_share_preference_title">This is a shared network</string>
|
||||
<!-- Summary for the preference to share a network with other users on the same device -->
|
||||
<string name="wifi_share_preference_summary">All users with the exception of guest users, will be able to view, edit and delete this network</string>
|
||||
<!-- Alert dialog title, informing the user to switch to the conflicting network. [CHAR LIMIT=250] -->
|
||||
<string name="wifi_conflict_dialog_title">Switch to <xliff:g id="shared">%1$s</xliff:g> \"<xliff:g id="network_name">%2$s</xliff:g>\" network?</string>
|
||||
<!-- Alert dialog summary, informing the user to switch to the conflicting network. [CHAR LIMIT=250] -->
|
||||
<string name="wifi_conflict_dialog_message">You\'re currently connected to a <xliff:g id="shared">%1$s</xliff:g> network with the same name. When you switch, you\'ll disconnect from the <xliff:g id="shared">%2$s</xliff:g> network and then reconnect to the <xliff:g id="private">%3$s</xliff:g> network. You may temporarily have no internet.</string>
|
||||
<!-- Hint for unchanged fields -->
|
||||
<string name="wifi_unchanged">(unchanged)</string>
|
||||
<!-- Label for the check box to allow other device users to edit network details -->
|
||||
|
||||
@@ -102,6 +102,12 @@
|
||||
android:entries="@array/wifi_privacy_entries_ext"
|
||||
android:entryValues="@array/wifi_privacy_values_ext"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="shared"
|
||||
android:icon="@drawable/ic_edit_24dp"
|
||||
android:title="@string/wifi_share_preference_title"
|
||||
android:summary="@string/wifi_share_preference_summary"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="edit_configuration"
|
||||
android:icon="@drawable/ic_edit_24dp"
|
||||
|
||||
@@ -55,6 +55,7 @@ import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.wifi.WepLessSecureWarningController;
|
||||
import com.android.settings.wifi.WifiConfigUiBase2;
|
||||
import com.android.settings.wifi.WifiDialog2;
|
||||
import com.android.settings.wifi.WifiPickerTrackerHelper;
|
||||
import com.android.settings.wifi.WifiUtils;
|
||||
import com.android.settings.wifi.details2.AddDevicePreferenceController2;
|
||||
import com.android.settings.wifi.details2.CertificateDetailsPreferenceController;
|
||||
@@ -66,6 +67,7 @@ import com.android.settings.wifi.details2.WifiMeteredPreferenceController2;
|
||||
import com.android.settings.wifi.details2.WifiPrivacyPreferenceController;
|
||||
import com.android.settings.wifi.details2.WifiPrivacyPreferenceController2;
|
||||
import com.android.settings.wifi.details2.WifiSecondSummaryController2;
|
||||
import com.android.settings.wifi.details2.WifiSharedPreferenceController;
|
||||
import com.android.settings.wifi.details2.WifiSubscriptionDetailPreferenceController2;
|
||||
import com.android.settings.wifi.repository.SharedConnectivityRepository;
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
@@ -100,6 +102,7 @@ public class WifiNetworkDetailsFragment extends RestrictedDashboardFragment impl
|
||||
public static final String KEY_HOTSPOT_DEVICE_BATTERY = "hotspot_device_details_battery";
|
||||
public static final String KEY_HOTSPOT_CONNECTION_CATEGORY = "hotspot_connection_category";
|
||||
public static final String KEY_EDIT_CONFIG_TOGGLE = "edit_configuration";
|
||||
public static final String KEY_SHARED_TOGGLE = "shared";
|
||||
|
||||
// Max age of tracked WifiEntries
|
||||
private static final long MAX_SCAN_AGE_MILLIS = 15_000;
|
||||
@@ -116,6 +119,8 @@ public class WifiNetworkDetailsFragment extends RestrictedDashboardFragment impl
|
||||
private List<WifiDialog2.WifiDialog2Listener> mWifiDialogListeners = new ArrayList<>();
|
||||
@VisibleForTesting
|
||||
List<AbstractPreferenceController> mControllers;
|
||||
@VisibleForTesting
|
||||
WifiPickerTrackerHelper mWifiPickerTrackerHelper;
|
||||
private boolean mIsInstantHotspotFeatureEnabled =
|
||||
SharedConnectivityRepository.isDeviceConfigEnabled();
|
||||
@VisibleForTesting
|
||||
@@ -289,6 +294,13 @@ public class WifiNetworkDetailsFragment extends RestrictedDashboardFragment impl
|
||||
context, KEY_EDIT_CONFIG_TOGGLE, wifiEntry);
|
||||
mControllers.add(wifiEditConfigPreferenceController);
|
||||
|
||||
mWifiPickerTrackerHelper =
|
||||
new WifiPickerTrackerHelper(getSettingsLifecycle(), getContext(), null);
|
||||
final WifiSharedPreferenceController wifiSharedPreferenceController =
|
||||
new WifiSharedPreferenceController(
|
||||
context, KEY_SHARED_TOGGLE, mWifiPickerTrackerHelper, wifiEntry);
|
||||
mControllers.add(wifiSharedPreferenceController);
|
||||
|
||||
final AddDevicePreferenceController2 addDevicePreferenceController2 =
|
||||
new AddDevicePreferenceController2(context);
|
||||
addDevicePreferenceController2.setWifiEntry(wifiEntry);
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.wifi.details2
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.android.settings.R
|
||||
import com.android.settings.core.SubSettingLauncher
|
||||
import com.android.settings.core.TogglePreferenceController
|
||||
import com.android.settings.wifi.WifiPickerTrackerHelper
|
||||
import com.android.settings.wifi.details.WifiNetworkDetailsFragment
|
||||
import com.android.wifitrackerlib.WifiEntry
|
||||
|
||||
class WifiSharedPreferenceController(
|
||||
context: Context,
|
||||
preferenceKey: String,
|
||||
private val wifiPickerTrackerHelper: WifiPickerTrackerHelper,
|
||||
private val wifiEntry: WifiEntry,
|
||||
) : TogglePreferenceController(context, preferenceKey) {
|
||||
|
||||
override fun getAvailabilityStatus(): Int {
|
||||
return if (com.android.settings.connectivity.Flags.wifiMultiuser()) {
|
||||
AVAILABLE
|
||||
} else {
|
||||
CONDITIONALLY_UNAVAILABLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun isChecked(): Boolean {
|
||||
return wifiEntry.isSharedWithOtherUsers()
|
||||
}
|
||||
|
||||
override fun setChecked(isChecked: Boolean): Boolean {
|
||||
val matchingWifiEntry: WifiEntry? = getMatchingWifiEntry(isChecked)
|
||||
var wifiConfiguration: WifiConfiguration? = matchingWifiEntry?.getWifiConfiguration()
|
||||
if (matchingWifiEntry != null && wifiConfiguration != null) {
|
||||
showAlertDialog(
|
||||
matchingWifiEntry.ssid ?: "",
|
||||
wifiConfiguration.shared,
|
||||
matchingWifiEntry.getKey(),
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getSliceHighlightMenuRes(): Int {
|
||||
return R.string.menu_key_network
|
||||
}
|
||||
|
||||
private fun showAlertDialog(ssid: String, shared: Boolean, key: String) {
|
||||
AlertDialog.Builder(mContext)
|
||||
.setTitle(
|
||||
mContext.getString(
|
||||
R.string.wifi_conflict_dialog_title,
|
||||
if (shared) mContext.getString(R.string.shared_message)
|
||||
else mContext.getString(R.string.private_message),
|
||||
ssid,
|
||||
)
|
||||
)
|
||||
.setMessage(
|
||||
mContext.getString(
|
||||
R.string.wifi_conflict_dialog_message,
|
||||
if (shared) mContext.getString(R.string.private_message)
|
||||
else mContext.getString(R.string.shared_message),
|
||||
if (shared) mContext.getString(R.string.private_message)
|
||||
else mContext.getString(R.string.shared_message),
|
||||
if (shared) mContext.getString(R.string.shared_message)
|
||||
else mContext.getString(R.string.private_message),
|
||||
)
|
||||
)
|
||||
.setPositiveButton(mContext.getString(R.string.wifi_conflict_dialog_confirm)) {
|
||||
dialog: DialogInterface,
|
||||
which: Int ->
|
||||
val bundle: Bundle = Bundle()
|
||||
bundle.putString(WifiNetworkDetailsFragment.KEY_CHOSEN_WIFIENTRY_KEY, key)
|
||||
|
||||
SubSettingLauncher(mContext)
|
||||
.setTitleText(mContext.getText(R.string.pref_title_network_details))
|
||||
.setDestination(WifiNetworkDetailsFragment::class.java.name)
|
||||
.setArguments(bundle)
|
||||
.setSourceMetricsCategory(metricsCategory)
|
||||
.launch()
|
||||
}
|
||||
.setNegativeButton(mContext.getString(R.string.wifi_conflict_dialog_cancel)) {
|
||||
dialog: DialogInterface,
|
||||
which: Int ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun getMatchingWifiEntry(shared: Boolean): WifiEntry? {
|
||||
val wifiPickerTracker = wifiPickerTrackerHelper.getWifiPickerTracker()
|
||||
val matchingWifiEntry: WifiEntry? =
|
||||
wifiPickerTracker.wifiEntries
|
||||
.stream()
|
||||
.filter { entry: WifiEntry -> TextUtils.equals(wifiEntry.ssid, entry.ssid) }
|
||||
.filter { entry: WifiEntry -> entry.getWifiConfiguration()?.shared == shared }
|
||||
.findFirst()
|
||||
.orElse(null)
|
||||
|
||||
return matchingWifiEntry
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.wifi.details2
|
||||
|
||||
import android.content.Context
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.platform.test.annotations.DisableFlags
|
||||
import android.platform.test.annotations.EnableFlags
|
||||
import android.platform.test.flag.junit.SetFlagsRule
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.R
|
||||
import com.android.settings.connectivity.Flags
|
||||
import com.android.settings.core.BasePreferenceController
|
||||
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat
|
||||
import com.android.settings.wifi.WifiPickerTrackerHelper
|
||||
import com.android.wifitrackerlib.WifiEntry
|
||||
import com.android.wifitrackerlib.WifiPickerTracker
|
||||
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.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.spy
|
||||
import org.mockito.kotlin.stub
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.shadows.ShadowDialog
|
||||
import org.robolectric.shadows.ShadowLooper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@Config(shadows = [ShadowAlertDialogCompat::class])
|
||||
class WifiSharedPreferenceControllerTest {
|
||||
@get:Rule val setFlagsRule = SetFlagsRule()
|
||||
|
||||
private var mockWifiEntry = mock<WifiEntry>()
|
||||
|
||||
private var mockWifiConfiguration = mock<WifiConfiguration>()
|
||||
|
||||
private val context: Context = spy(RuntimeEnvironment.application)
|
||||
|
||||
private val mockWifiPickerTracker: WifiPickerTracker = mock<WifiPickerTracker>()
|
||||
|
||||
private val mockWifiPickerTrackerHelper: WifiPickerTrackerHelper =
|
||||
mock<WifiPickerTrackerHelper>()
|
||||
|
||||
private var controller =
|
||||
WifiSharedPreferenceController(
|
||||
context,
|
||||
"share_configuration",
|
||||
mockWifiPickerTrackerHelper,
|
||||
mockWifiEntry,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockWifiEntry.stub { on { getWifiConfiguration() } doReturn mockWifiConfiguration }
|
||||
mockWifiEntry.stub { on { getSsid() } doReturn "testSSID" }
|
||||
mockWifiEntry.stub { on { getKey() } doReturn "testKey" }
|
||||
|
||||
context.setTheme(R.style.Theme_Settings_Home)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isChecked_returnsWifiEntry_allowEditConfig_Value() {
|
||||
mockWifiEntry.stub { on { isSharedWithOtherUsers() } doReturn false }
|
||||
|
||||
assertThat(controller.isChecked()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setChecked_conflictingEntry_showsAlertDialog_validateMessages() {
|
||||
mockWifiConfiguration.shared = true
|
||||
mockWifiPickerTrackerHelper.stub {
|
||||
on { getWifiPickerTracker() } doReturn mockWifiPickerTracker
|
||||
}
|
||||
whenever(mockWifiPickerTracker.wifiEntries).thenReturn(listOf(mockWifiEntry))
|
||||
ShadowDialog.reset()
|
||||
|
||||
controller.setChecked(true)
|
||||
|
||||
val dialog = ShadowAlertDialogCompat.getLatestAlertDialog()
|
||||
val shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog)
|
||||
assertThat(shadowDialog).isNotNull()
|
||||
assertThat(shadowDialog.getTitle().toString())
|
||||
.isEqualTo(context.getString(R.string.wifi_conflict_dialog_title, "shared", "testSSID"))
|
||||
assertThat(shadowDialog.getMessage().toString())
|
||||
.isEqualTo(
|
||||
context.getString(
|
||||
R.string.wifi_conflict_dialog_message,
|
||||
"private",
|
||||
"private",
|
||||
"shared",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setChecked_conflictingEntry_showsAlertDialog_clickNegativeButton() {
|
||||
mockWifiConfiguration.shared = true
|
||||
mockWifiPickerTrackerHelper.stub {
|
||||
on { getWifiPickerTracker() } doReturn mockWifiPickerTracker
|
||||
}
|
||||
whenever(mockWifiPickerTracker.wifiEntries).thenReturn(listOf(mockWifiEntry))
|
||||
ShadowDialog.reset()
|
||||
|
||||
controller.setChecked(true)
|
||||
|
||||
var dialog: AlertDialog? = ShadowAlertDialogCompat.getLatestAlertDialog()
|
||||
assertThat(dialog).isNotNull()
|
||||
dialog?.getButton(AlertDialog.BUTTON_NEGATIVE)?.performClick()
|
||||
ShadowLooper.idleMainLooper()
|
||||
|
||||
assertThat(dialog?.isShowing()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setChecked_noConflict_doesNotShowAlertDialog() {
|
||||
mockWifiConfiguration.shared = true
|
||||
mockWifiPickerTrackerHelper.stub {
|
||||
on { getWifiPickerTracker() } doReturn mockWifiPickerTracker
|
||||
}
|
||||
whenever(mockWifiPickerTracker.wifiEntries).thenReturn(listOf())
|
||||
ShadowDialog.reset()
|
||||
|
||||
controller.setChecked(true)
|
||||
|
||||
val dialog = ShadowAlertDialogCompat.getLatestAlertDialog()
|
||||
assertThat(dialog).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisableFlags(Flags.FLAG_WIFI_MULTIUSER)
|
||||
fun getAvailabilityStatus_flagDisabled() {
|
||||
assertThat(controller.getAvailabilityStatus())
|
||||
.isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_WIFI_MULTIUSER)
|
||||
fun getAvailabilityStatus_flagEnabled() {
|
||||
assertThat(controller.getAvailabilityStatus()).isEqualTo(BasePreferenceController.AVAILABLE)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user