1 /*
<lambda>null2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.settings.wifi.details2
18
19 import android.content.Context
20 import android.net.wifi.WifiConfiguration
21 import android.net.wifi.WifiManager
22 import android.os.Bundle
23 import android.os.Handler
24 import android.os.HandlerThread
25 import android.os.Looper
26 import android.os.Process
27 import android.os.SimpleClock
28 import androidx.compose.foundation.layout.Column
29 import androidx.compose.foundation.layout.Spacer
30 import androidx.compose.foundation.layout.width
31 import androidx.compose.runtime.Composable
32 import androidx.compose.runtime.getValue
33 import androidx.compose.runtime.mutableIntStateOf
34 import androidx.compose.runtime.mutableStateOf
35 import androidx.compose.runtime.remember
36 import androidx.compose.runtime.saveable.rememberSaveable
37 import androidx.compose.runtime.setValue
38 import androidx.compose.ui.Modifier
39 import androidx.compose.ui.platform.LocalContext
40 import androidx.compose.ui.platform.LocalLifecycleOwner
41 import androidx.compose.ui.res.stringArrayResource
42 import androidx.compose.ui.res.stringResource
43 import androidx.navigation.NavType
44 import androidx.navigation.navArgument
45 import com.android.settings.R
46 import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
47 import com.android.settingslib.spa.framework.common.SettingsPageProvider
48 import com.android.settingslib.spa.framework.theme.SettingsDimension
49 import com.android.settingslib.spa.widget.preference.ListPreferenceModel
50 import com.android.settingslib.spa.widget.preference.ListPreferenceOption
51 import com.android.settingslib.spa.widget.preference.RadioPreferences
52 import com.android.settingslib.spa.widget.preference.SwitchPreference
53 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
54 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
55 import com.android.settingslib.spa.widget.ui.CategoryTitle
56 import com.android.wifitrackerlib.WifiEntry
57 import java.time.Clock
58 import java.time.ZoneOffset
59
60 const val WIFI_ENTRY_KEY = "wifiEntryKey"
61
62 object WifiPrivacyPageProvider : SettingsPageProvider {
63 override val name = "WifiPrivacy"
64 const val TAG = "WifiPrivacyPageProvider"
65
66 override val parameter = listOf(
67 navArgument(WIFI_ENTRY_KEY) { type = NavType.StringType },
68 )
69
70 @Composable
71 override fun Page(arguments: Bundle?) {
72 val wifiEntryKey = arguments!!.getString(WIFI_ENTRY_KEY)
73 if (wifiEntryKey != null) {
74 val context = LocalContext.current
75 val lifecycle = LocalLifecycleOwner.current.lifecycle
76 val wifiEntry = remember {
77 getWifiEntry(context, wifiEntryKey, lifecycle)
78 }
79 WifiPrivacyPage(wifiEntry)
80 }
81 }
82
83 fun getRoute(
84 wifiEntryKey: String,
85 ): String = "${name}/$wifiEntryKey"
86 }
87
88 @Composable
WifiPrivacyPagenull89 fun WifiPrivacyPage(wifiEntry: WifiEntry) {
90 val isSelectable: Boolean = wifiEntry.canSetPrivacy()
91 RegularScaffold(
92 title = stringResource(id = R.string.wifi_privacy_settings)
93 ) {
94 Column {
95 val title = stringResource(id = R.string.wifi_privacy_mac_settings)
96 val wifiPrivacyEntries = stringArrayResource(R.array.wifi_privacy_entries)
97 val wifiPrivacyValues = stringArrayResource(R.array.wifi_privacy_values)
98 val textsSelectedId = rememberSaveable { mutableIntStateOf(wifiEntry.privacy) }
99 val dataList = remember {
100 wifiPrivacyEntries.mapIndexed { index, text ->
101 ListPreferenceOption(id = wifiPrivacyValues[index].toInt(), text = text)
102 }
103 }
104 RadioPreferences(remember {
105 object : ListPreferenceModel {
106 override val title = title
107 override val options = dataList
108 override val selectedId = textsSelectedId
109 override val onIdSelected: (Int) -> Unit = {
110 textsSelectedId.intValue = it
111 onSelectedChange(wifiEntry, it)
112 }
113 override val enabled = { isSelectable }
114 }
115 })
116 wifiEntry.wifiConfiguration?.let {
117 DeviceNameSwitchPreference(it)
118 }
119 }
120 }
121 }
122
123 @Composable
DeviceNameSwitchPreferencenull124 fun DeviceNameSwitchPreference(wifiConfiguration: WifiConfiguration){
125 Spacer(modifier = Modifier.width(SettingsDimension.itemDividerHeight))
126 CategoryTitle(title = stringResource(R.string.wifi_privacy_device_name_settings))
127 Spacer(modifier = Modifier.width(SettingsDimension.itemDividerHeight))
128 var checked by remember {
129 mutableStateOf(wifiConfiguration.isSendDhcpHostnameEnabled)
130 }
131 val context = LocalContext.current
132 val wifiManager = context.getSystemService(WifiManager::class.java)!!
133 SwitchPreference(object : SwitchPreferenceModel {
134 override val title =
135 context.resources.getString(
136 R.string.wifi_privacy_send_device_name_toggle_title
137 )
138 override val summary =
139 {
140 context.resources.getString(
141 R.string.wifi_privacy_send_device_name_toggle_summary
142 )
143 }
144 override val checked = { checked }
145 override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
146 wifiConfiguration.isSendDhcpHostnameEnabled = newChecked
147 wifiManager.save(wifiConfiguration, null /* listener */)
148 checked = newChecked
149 }
150 })
151 }
152
onSelectedChangenull153 fun onSelectedChange(wifiEntry: WifiEntry, privacy: Int) {
154 if (wifiEntry.privacy == privacy) {
155 // Prevent disconnection + reconnection if settings not changed.
156 return
157 }
158 wifiEntry.setPrivacy(privacy)
159
160 // To activate changing, we need to reconnect network. WiFi will auto connect to
161 // current network after disconnect(). Only needed when this is connected network.
162
163 // To activate changing, we need to reconnect network. WiFi will auto connect to
164 // current network after disconnect(). Only needed when this is connected network.
165 if (wifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) {
166 wifiEntry.disconnect(null /* callback */)
167 wifiEntry.connect(null /* callback */)
168 }
169 }
170
getWifiEntrynull171 fun getWifiEntry(
172 context: Context,
173 wifiEntryKey: String,
174 liftCycle: androidx.lifecycle.Lifecycle
175 ): WifiEntry {
176 // Max age of tracked WifiEntries
177 val MAX_SCAN_AGE_MILLIS: Long = 15000
178 // Interval between initiating SavedNetworkTracker scans
179 val SCAN_INTERVAL_MILLIS: Long = 10000
180 val mWorkerThread = HandlerThread(
181 WifiPrivacyPageProvider.TAG,
182 Process.THREAD_PRIORITY_BACKGROUND
183 )
184 mWorkerThread.start()
185 val elapsedRealtimeClock: Clock = object : SimpleClock(ZoneOffset.UTC) {
186 override fun millis(): Long {
187 return android.os.SystemClock.elapsedRealtime()
188 }
189 }
190 val mNetworkDetailsTracker = featureFactory
191 .wifiTrackerLibProvider
192 .createNetworkDetailsTracker(
193 liftCycle,
194 context,
195 Handler(Looper.getMainLooper()),
196 mWorkerThread.getThreadHandler(),
197 elapsedRealtimeClock,
198 MAX_SCAN_AGE_MILLIS,
199 SCAN_INTERVAL_MILLIS,
200 wifiEntryKey
201 )
202 return mNetworkDetailsTracker.wifiEntry
203 }
204