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