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