1 /* 2 * Copyright (C) 2019 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.dpp; 18 19 import android.app.Activity; 20 import android.app.settings.SettingsEnums; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.net.ConnectivityManager; 24 import android.net.wifi.WifiConfiguration; 25 import android.net.wifi.WifiManager; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.os.HandlerThread; 29 import android.os.Looper; 30 import android.os.Process; 31 import android.os.SimpleClock; 32 import android.os.SystemClock; 33 import android.widget.Toast; 34 35 import androidx.annotation.VisibleForTesting; 36 import androidx.preference.Preference; 37 import androidx.preference.PreferenceCategory; 38 39 import com.android.settings.R; 40 import com.android.settings.SettingsPreferenceFragment; 41 import com.android.settings.core.SubSettingLauncher; 42 import com.android.settings.wifi.AddNetworkFragment; 43 import com.android.settings.wifi.WifiEntryPreference; 44 import com.android.wifitrackerlib.SavedNetworkTracker; 45 import com.android.wifitrackerlib.WifiEntry; 46 47 import java.time.Clock; 48 import java.time.ZoneOffset; 49 import java.util.List; 50 import java.util.stream.Collectors; 51 52 public class WifiNetworkListFragment extends SettingsPreferenceFragment implements 53 SavedNetworkTracker.SavedNetworkTrackerCallback, Preference.OnPreferenceClickListener { 54 private static final String TAG = "WifiNetworkListFragment"; 55 56 @VisibleForTesting static final String WIFI_CONFIG_KEY = "wifi_config_key"; 57 private static final String PREF_KEY_ACCESS_POINTS = "access_points"; 58 59 @VisibleForTesting 60 static final int ADD_NETWORK_REQUEST = 1; 61 62 @VisibleForTesting PreferenceCategory mPreferenceGroup; 63 @VisibleForTesting Preference mAddPreference; 64 65 @VisibleForTesting WifiManager mWifiManager; 66 67 private WifiManager.ActionListener mSaveListener; 68 69 // Max age of tracked WifiEntries 70 private static final long MAX_SCAN_AGE_MILLIS = 15_000; 71 // Interval between initiating SavedNetworkTracker scans 72 private static final long SCAN_INTERVAL_MILLIS = 10_000; 73 74 @VisibleForTesting SavedNetworkTracker mSavedNetworkTracker; 75 @VisibleForTesting HandlerThread mWorkerThread; 76 77 // Container Activity must implement this interface 78 public interface OnChooseNetworkListener { onChooseNetwork(WifiNetworkConfig wifiNetworkConfig)79 void onChooseNetwork(WifiNetworkConfig wifiNetworkConfig); 80 } 81 @VisibleForTesting OnChooseNetworkListener mOnChooseNetworkListener; 82 83 @Override getMetricsCategory()84 public int getMetricsCategory() { 85 return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR; 86 } 87 88 private static class DisableUnreachableWifiEntryPreference extends WifiEntryPreference { DisableUnreachableWifiEntryPreference(Context context, WifiEntry entry)89 DisableUnreachableWifiEntryPreference(Context context, WifiEntry entry) { 90 super(context, entry); 91 } 92 93 @Override onUpdated()94 public void onUpdated() { 95 super.onUpdated(); 96 this.setEnabled(getWifiEntry().getLevel() != WifiEntry.WIFI_LEVEL_UNREACHABLE); 97 } 98 } 99 100 @Override onAttach(Context context)101 public void onAttach(Context context) { 102 super.onAttach(context); 103 if (!(context instanceof OnChooseNetworkListener)) { 104 throw new IllegalArgumentException("Invalid context type"); 105 } 106 mOnChooseNetworkListener = (OnChooseNetworkListener) context; 107 } 108 109 @Override onDetach()110 public void onDetach() { 111 mOnChooseNetworkListener = null; 112 super.onDetach(); 113 } 114 115 @Override onActivityCreated(Bundle savedInstanceState)116 public void onActivityCreated(Bundle savedInstanceState) { 117 super.onActivityCreated(savedInstanceState); 118 119 final Context context = getContext(); 120 mWifiManager = context.getSystemService(WifiManager.class); 121 122 mSaveListener = new WifiManager.ActionListener() { 123 @Override 124 public void onSuccess() { 125 // Do nothing. 126 } 127 128 @Override 129 public void onFailure(int reason) { 130 final Activity activity = getActivity(); 131 if (activity != null && !activity.isFinishing()) { 132 Toast.makeText(activity, R.string.wifi_failed_save_message, 133 Toast.LENGTH_SHORT).show(); 134 } 135 } 136 }; 137 138 mWorkerThread = new HandlerThread(TAG 139 + "{" + Integer.toHexString(System.identityHashCode(this)) + "}", 140 Process.THREAD_PRIORITY_BACKGROUND); 141 mWorkerThread.start(); 142 final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) { 143 @Override 144 public long millis() { 145 return SystemClock.elapsedRealtime(); 146 } 147 }; 148 mSavedNetworkTracker = new SavedNetworkTracker(getSettingsLifecycle(), context, 149 context.getSystemService(WifiManager.class), 150 context.getSystemService(ConnectivityManager.class), 151 new Handler(Looper.getMainLooper()), 152 mWorkerThread.getThreadHandler(), 153 elapsedRealtimeClock, 154 MAX_SCAN_AGE_MILLIS, 155 SCAN_INTERVAL_MILLIS, 156 this); 157 } 158 159 @Override onDestroyView()160 public void onDestroyView() { 161 mWorkerThread.quit(); 162 163 super.onDestroyView(); 164 } 165 166 @Override onActivityResult(int requestCode, int resultCode, Intent data)167 public void onActivityResult(int requestCode, int resultCode, Intent data) { 168 super.onActivityResult(requestCode, resultCode, data); 169 170 if (requestCode == ADD_NETWORK_REQUEST && resultCode == Activity.RESULT_OK) { 171 final WifiConfiguration wifiConfiguration = data.getParcelableExtra(WIFI_CONFIG_KEY); 172 if (wifiConfiguration != null) { 173 mWifiManager.save(wifiConfiguration, mSaveListener); 174 } 175 } 176 } 177 178 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)179 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 180 addPreferencesFromResource(R.xml.wifi_dpp_network_list); 181 182 mPreferenceGroup = findPreference(PREF_KEY_ACCESS_POINTS); 183 184 mAddPreference = new Preference(getPrefContext()); 185 mAddPreference.setIcon(R.drawable.ic_add_24dp); 186 mAddPreference.setTitle(R.string.wifi_add_network); 187 mAddPreference.setOnPreferenceClickListener(this); 188 } 189 190 /** Called when the state of Wifi has changed. */ 191 @Override onSavedWifiEntriesChanged()192 public void onSavedWifiEntriesChanged() { 193 final List<WifiEntry> savedWifiEntries = mSavedNetworkTracker.getSavedWifiEntries().stream() 194 .filter(entry -> isValidForDppConfiguration(entry)) 195 .collect(Collectors.toList()); 196 197 int index = 0; 198 mPreferenceGroup.removeAll(); 199 for (WifiEntry savedEntry : savedWifiEntries) { 200 final DisableUnreachableWifiEntryPreference preference = 201 new DisableUnreachableWifiEntryPreference(getContext(), savedEntry); 202 preference.setOnPreferenceClickListener(this); 203 preference.setEnabled(savedEntry.getLevel() != WifiEntry.WIFI_LEVEL_UNREACHABLE); 204 preference.setOrder(index++); 205 206 mPreferenceGroup.addPreference(preference); 207 } 208 209 mAddPreference.setOrder(index); 210 mPreferenceGroup.addPreference(mAddPreference); 211 } 212 213 @Override onSubscriptionWifiEntriesChanged()214 public void onSubscriptionWifiEntriesChanged() { 215 // Do nothing. 216 } 217 218 @Override onWifiStateChanged()219 public void onWifiStateChanged() { 220 // Do nothing. 221 } 222 223 @Override onPreferenceClick(Preference preference)224 public boolean onPreferenceClick(Preference preference) { 225 if (preference instanceof WifiEntryPreference) { 226 final WifiEntry selectedWifiEntry = ((WifiEntryPreference) preference).getWifiEntry(); 227 228 // Launch WifiDppAddDeviceFragment to start DPP in Configurator-Initiator role. 229 final WifiConfiguration wifiConfig = selectedWifiEntry.getWifiConfiguration(); 230 if (wifiConfig == null) { 231 throw new IllegalArgumentException("Invalid access point"); 232 } 233 final WifiNetworkConfig networkConfig = WifiNetworkConfig.getValidConfigOrNull( 234 WifiDppUtils.getSecurityString(selectedWifiEntry), 235 wifiConfig.getPrintableSsid(), wifiConfig.preSharedKey, wifiConfig.hiddenSSID, 236 wifiConfig.networkId, /* isHotspot */ false); 237 if (mOnChooseNetworkListener != null) { 238 mOnChooseNetworkListener.onChooseNetwork(networkConfig); 239 } 240 } else if (preference == mAddPreference) { 241 new SubSettingLauncher(getContext()) 242 .setTitleRes(R.string.wifi_add_network) 243 .setDestination(AddNetworkFragment.class.getName()) 244 .setSourceMetricsCategory(getMetricsCategory()) 245 .setResultListener(this, ADD_NETWORK_REQUEST) 246 .launch(); 247 } else { 248 return super.onPreferenceTreeClick(preference); 249 } 250 return true; 251 } 252 isValidForDppConfiguration(WifiEntry wifiEntry)253 private boolean isValidForDppConfiguration(WifiEntry wifiEntry) { 254 final int security = wifiEntry.getSecurity(); 255 256 // DPP 1.0 only support PSK and SAE. 257 return security == WifiEntry.SECURITY_PSK || security == WifiEntry.SECURITY_SAE; 258 } 259 } 260